diff options
author | Christian Kandeler <christian.kandeler@qt.io> | 2023-05-22 16:15:44 +0200 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@qt.io> | 2023-05-23 10:52:39 +0000 |
commit | 54766c9351bb641b5df3360b2452755dceddab75 (patch) | |
tree | dd80601a5534ddb241b7e512ad40f8f74a7df390 /src | |
parent | cacab19b31ebfa0f3676643031a56d3a8ccd2bea (diff) |
Loader: Move product collection into its own class
Change-Id: I249bc3d77ff007517aa352e333585fd8ec5f7ef0
Reviewed-by: Ivan Komissarov <ABBAPOH@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/corelib/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/lib/corelib/corelib.qbs | 2 | ||||
-rw-r--r-- | src/lib/corelib/loader/itemreader.cpp | 12 | ||||
-rw-r--r-- | src/lib/corelib/loader/itemreader.h | 1 | ||||
-rw-r--r-- | src/lib/corelib/loader/loaderutils.cpp | 11 | ||||
-rw-r--r-- | src/lib/corelib/loader/loaderutils.h | 12 | ||||
-rw-r--r-- | src/lib/corelib/loader/productscollector.cpp | 730 | ||||
-rw-r--r-- | src/lib/corelib/loader/productscollector.h | 82 | ||||
-rw-r--r-- | src/lib/corelib/loader/projecttreebuilder.cpp | 680 |
9 files changed, 866 insertions, 666 deletions
diff --git a/src/lib/corelib/CMakeLists.txt b/src/lib/corelib/CMakeLists.txt index 895853dab..d8c67af9b 100644 --- a/src/lib/corelib/CMakeLists.txt +++ b/src/lib/corelib/CMakeLists.txt @@ -266,6 +266,8 @@ set(LOADER_SOURCES probesresolver.h productitemmultiplexer.cpp productitemmultiplexer.h + productscollector.cpp + productscollector.h projectresolver.cpp projectresolver.h projecttreebuilder.cpp diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs index 5fc2680c0..6904ec3ee 100644 --- a/src/lib/corelib/corelib.qbs +++ b/src/lib/corelib/corelib.qbs @@ -355,6 +355,8 @@ QbsLibrary { "probesresolver.h", "productitemmultiplexer.cpp", "productitemmultiplexer.h", + "productscollector.cpp", + "productscollector.h", "projectresolver.cpp", "projectresolver.h", "projecttreebuilder.cpp", diff --git a/src/lib/corelib/loader/itemreader.cpp b/src/lib/corelib/loader/itemreader.cpp index 9eb08d8b9..60bbcebb2 100644 --- a/src/lib/corelib/loader/itemreader.cpp +++ b/src/lib/corelib/loader/itemreader.cpp @@ -219,6 +219,18 @@ Item *ItemReader::setupItemFromFile( return item; } +Item *ItemReader::wrapInProjectIfNecessary(Item *item, const SetupProjectParameters ¶meters) +{ + if (item->type() == ItemType::Project) + return item; + Item *prj = Item::create(item->pool(), ItemType::Project); + Item::addChild(prj, item); + prj->setFile(item->file()); + prj->setLocation(item->location()); + prj->setupForBuiltinType(parameters.deprecationWarningMode(), m_visitorState->logger()); + return prj; +} + QStringList ItemReader::readExtraSearchPaths(Item *item, Evaluator &evaluator, bool *wasSet) { QStringList result; diff --git a/src/lib/corelib/loader/itemreader.h b/src/lib/corelib/loader/itemreader.h index 7c5d7d11c..b92ece483 100644 --- a/src/lib/corelib/loader/itemreader.h +++ b/src/lib/corelib/loader/itemreader.h @@ -84,6 +84,7 @@ public: Item *setupItemFromFile(const QString &filePath, const CodeLocation &referencingLocation, Evaluator &evaluator); + Item *wrapInProjectIfNecessary(Item *item, const SetupProjectParameters ¶meters); QStringList readExtraSearchPaths(Item *item, Evaluator &evaluator, bool *wasSet = nullptr); Set<QString> filesRead() const; diff --git a/src/lib/corelib/loader/loaderutils.cpp b/src/lib/corelib/loader/loaderutils.cpp index b22f644ae..2a91640dd 100644 --- a/src/lib/corelib/loader/loaderutils.cpp +++ b/src/lib/corelib/loader/loaderutils.cpp @@ -47,6 +47,8 @@ #include <language/value.h> #include <logging/categories.h> #include <logging/translator.h> +#include <tools/progressobserver.h> +#include <tools/setupprojectparameters.h> #include <tools/stringconstants.h> namespace qbs::Internal { @@ -127,4 +129,13 @@ bool TopLevelProjectContext::checkItemCondition(Item *item, Evaluator &evaluator return false; } +void TopLevelProjectContext::checkCancelation(const SetupProjectParameters ¶meters) +{ + if (progressObserver && progressObserver->canceled()) { + throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.") + .arg(TopLevelProject::deriveId( + parameters.finalBuildConfigurationTree()))); + } +} + } // namespace qbs::Internal diff --git a/src/lib/corelib/loader/loaderutils.h b/src/lib/corelib/loader/loaderutils.h index 15e36b014..bf3a55adb 100644 --- a/src/lib/corelib/loader/loaderutils.h +++ b/src/lib/corelib/loader/loaderutils.h @@ -50,10 +50,13 @@ #include <vector> -namespace qbs::Internal { +namespace qbs { +class SetupProjectParameters; +namespace Internal { class Evaluator; class Item; class ProductContext; +class ProgressObserver; class ProjectContext; using ModulePropertiesPerGroup = std::unordered_map<const Item *, QualifiedIdSet>; @@ -102,16 +105,20 @@ public: ~TopLevelProjectContext() { qDeleteAll(projects); } bool checkItemCondition(Item *item, Evaluator &evaluator); + void checkCancelation(const SetupProjectParameters ¶meters); std::vector<ProjectContext *> projects; std::list<std::pair<ProductContext *, int>> productsToHandle; std::multimap<QString, ProductContext *> productsByName; std::unordered_map<Item *, ProductInfo> productInfos; + Set<QString> projectNamesUsedInOverrides; + Set<QString> productNamesUsedInOverrides; Set<Item *> disabledItems; Set<QString> erroneousProducts; std::vector<ProbeConstPtr> probes; QString buildDirectory; QVariantMap profileConfigs; + ProgressObserver *progressObserver = nullptr; // For fast look-up when resolving Depends.productTypes. // The contract is that it contains fully handled, error-free, enabled products. @@ -133,4 +140,5 @@ void mergeParameters(QVariantMap &dst, const QVariantMap &src); ShadowProductInfo getShadowProductInfo(const ProductContext &product); void adjustParametersScopes(Item *item, Item *scope); -} // namespace qbs::Internal +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/loader/productscollector.cpp b/src/lib/corelib/loader/productscollector.cpp new file mode 100644 index 000000000..ee37eda8b --- /dev/null +++ b/src/lib/corelib/loader/productscollector.cpp @@ -0,0 +1,730 @@ +/**************************************************************************** +** +** 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 "productscollector.h" + +#include "dependenciesresolver.h" +#include "itemreader.h" +#include "loaderutils.h" +#include "localprofiles.h" +#include "productitemmultiplexer.h" +#include "probesresolver.h" + +#include <language/builtindeclarations.h> +#include <language/evaluator.h> +#include <language/filecontext.h> +#include <language/item.h> +#include <language/language.h> +#include <language/value.h> +#include <logging/categories.h> +#include <logging/translator.h> +#include <tools/fileinfo.h> +#include <tools/profile.h> +#include <tools/profiling.h> +#include <language/scriptengine.h> +#include <tools/set.h> +#include <tools/settings.h> +#include <tools/setupprojectparameters.h> +#include <tools/stringconstants.h> + +#include <QDir> +#include <QDirIterator> + +namespace qbs::Internal { + +class ProductsCollector::Private +{ +public: + Private(const SetupProjectParameters ¶meters, TopLevelProjectContext &topLevelProject, + DependenciesResolver &dependenciesResolver, Evaluator &evaluator, + ItemReader &itemReader, ProbesResolver &probesResolver, + LocalProfiles &localProfiles, ProductItemMultiplexer &multiplexer, Logger &logger) + : parameters(parameters), topLevelProject(topLevelProject), + dependenciesResolver(dependenciesResolver), evaluator(evaluator), + itemReader(itemReader), probesResolver(probesResolver), + localProfiles(localProfiles), multiplexer(multiplexer), logger(logger) {} + + void handleProject(Item *projectItem, const Set<QString> &referencedFilePaths); + QList<Item *> multiplexProductItem(ProductContext &dummyContext, Item *productItem); + void prepareProduct(ProjectContext &projectContext, Item *productItem); + void handleSubProject(ProjectContext &projectContext, Item *projectItem, + const Set<QString> &referencedFilePaths); + void copyProperties(const Item *sourceProject, Item *targetProject); + QList<Item *> loadReferencedFile( + const QString &relativePath, const CodeLocation &referencingLocation, + const Set<QString> &referencedFilePaths, ProductContext &dummyContext); + bool mergeExportItems(ProductContext &productContext); + bool checkExportItemCondition(Item *exportItem, const ProductContext &product); + void initProductProperties(const ProductContext &product); + void checkProjectNamesInOverrides(); + void collectProductsByName(); + void checkProductNamesInOverrides(); + + const SetupProjectParameters ¶meters; + TopLevelProjectContext &topLevelProject; + DependenciesResolver &dependenciesResolver; + Evaluator &evaluator; + ItemReader &itemReader; + ProbesResolver &probesResolver; + LocalProfiles &localProfiles; + ProductItemMultiplexer &multiplexer; + Logger &logger; + Settings settings{parameters.settingsDirectory()}; + Set<QString> disabledProjects; + Version qbsVersion; + Item *tempScopeItem = nullptr; + qint64 elapsedTimePrepareProducts = 0; + +private: + class TempBaseModuleAttacher { + public: + TempBaseModuleAttacher(Private *d, ProductContext &product); + ~TempBaseModuleAttacher() { drop(); } + void drop(); + Item *tempBaseModuleItem() const { return m_tempBaseModule; } + + private: + Item * const m_productItem; + ValuePtr m_origQbsValue; + Item *m_tempBaseModule = nullptr; + }; +}; + +ProductsCollector::ProductsCollector(const SetupProjectParameters ¶meters, + TopLevelProjectContext &topLevelProject, DependenciesResolver &dependenciesResolver, + Evaluator &evaluator, ItemReader &itemReader, ProbesResolver &probesResolver, + LocalProfiles &localProfiles, ProductItemMultiplexer &multiplexer, Logger &logger) + : d(makePimpl<Private>(parameters, topLevelProject, dependenciesResolver, evaluator, + itemReader, probesResolver, localProfiles, multiplexer, logger)) {} + +ProductsCollector::~ProductsCollector() = default; + +void ProductsCollector::run(Item *rootProject) +{ + d->handleProject(rootProject, {QDir::cleanPath(d->parameters.projectFilePath())}); + d->checkProjectNamesInOverrides(); + d->collectProductsByName(); + d->checkProductNamesInOverrides(); +} + +void ProductsCollector::printProfilingInfo(int indent) +{ + if (!d->parameters.logElapsedTime()) + return; + const QByteArray prefix(indent, ' '); + d->logger.qbsLog(LoggerInfo, true) + << prefix + << Tr::tr("Preparing products took %1.") + .arg(elapsedTimeString(d->elapsedTimePrepareProducts)); +} + +void ProductsCollector::Private::handleProject(Item *projectItem, + const Set<QString> &referencedFilePaths) +{ + auto p = std::make_unique<ProjectContext>(); + auto &projectContext = *p; + projectContext.topLevelProject = &topLevelProject; + ItemValuePtr itemValue = ItemValue::create(projectItem); + projectContext.scope = Item::create(projectItem->pool(), ItemType::Scope); + projectContext.scope->setFile(projectItem->file()); + projectContext.scope->setProperty(StringConstants::projectVar(), itemValue); + ProductContext dummyProduct; + dummyProduct.project = &projectContext; + dummyProduct.moduleProperties = parameters.finalBuildConfigurationTree(); + dummyProduct.profileModuleProperties = dummyProduct.moduleProperties; + dummyProduct.profileName = parameters.topLevelProfile(); + dependenciesResolver.loadBaseModule(dummyProduct, projectItem); + + projectItem->overrideProperties(parameters.overriddenValuesTree(), + StringConstants::projectPrefix(), parameters, logger); + projectContext.name = evaluator.stringValue(projectItem, StringConstants::nameProperty()); + if (projectContext.name.isEmpty()) { + projectContext.name = FileInfo::baseName(projectItem->location().filePath()); + projectItem->setProperty(StringConstants::nameProperty(), + VariantValue::create(projectContext.name)); + } + projectItem->overrideProperties(parameters.overriddenValuesTree(), + StringConstants::projectsOverridePrefix() + projectContext.name, + parameters, logger); + if (!topLevelProject.checkItemCondition(projectItem, evaluator)) { + disabledProjects.insert(projectContext.name); + return; + } + topLevelProject.projects.push_back(p.release()); + SearchPathsManager searchPathsManager(itemReader, itemReader.readExtraSearchPaths(projectItem, + evaluator) + << projectItem->file()->dirPath()); + projectContext.searchPathsStack = itemReader.extraSearchPathsStack(); + projectContext.item = projectItem; + + const QString minVersionStr + = evaluator.stringValue(projectItem, StringConstants::minimumQbsVersionProperty(), + QStringLiteral("1.3.0")); + const Version minVersion = Version::fromString(minVersionStr); + if (!minVersion.isValid()) { + throw ErrorInfo(Tr::tr("The value '%1' of Project.minimumQbsVersion " + "is not a valid version string.") + .arg(minVersionStr), projectItem->location()); + } + if (!qbsVersion.isValid()) + qbsVersion = Version::fromString(QLatin1String(QBS_VERSION)); + if (qbsVersion < minVersion) { + throw ErrorInfo(Tr::tr("The project requires at least qbs version %1, but " + "this is qbs version %2.").arg(minVersion.toString(), + qbsVersion.toString())); + } + + for (Item * const child : projectItem->children()) + child->setScope(projectContext.scope); + + projectContext.topLevelProject->probes << probesResolver.resolveProbes( + {dummyProduct.name, dummyProduct.uniqueName()}, projectItem); + + localProfiles.collectProfilesFromItems(projectItem, projectContext.scope); + + QList<Item *> multiplexedProducts; + for (Item * const child : projectItem->children()) { + if (child->type() == ItemType::Product) + multiplexedProducts << multiplexProductItem(dummyProduct, child); + } + for (Item * const additionalProductItem : std::as_const(multiplexedProducts)) + Item::addChild(projectItem, additionalProductItem); + + const QList<Item *> originalChildren = projectItem->children(); + for (Item * const child : originalChildren) { + switch (child->type()) { + case ItemType::Product: + prepareProduct(projectContext, child); + break; + case ItemType::SubProject: + handleSubProject(projectContext, child, referencedFilePaths); + break; + case ItemType::Project: + copyProperties(projectItem, child); + handleProject(child, referencedFilePaths); + break; + default: + break; + } + } + + const QStringList refs = evaluator.stringListValue( + projectItem, StringConstants::referencesProperty()); + const CodeLocation referencingLocation + = projectItem->property(StringConstants::referencesProperty())->location(); + QList<Item *> additionalProjectChildren; + for (const QString &filePath : refs) { + try { + additionalProjectChildren << loadReferencedFile( + filePath, referencingLocation, referencedFilePaths, dummyProduct); + } catch (const ErrorInfo &error) { + if (parameters.productErrorMode() == ErrorHandlingMode::Strict) + throw; + logger.printWarning(error); + } + } + for (Item * const subItem : std::as_const(additionalProjectChildren)) { + Item::addChild(projectContext.item, subItem); + switch (subItem->type()) { + case ItemType::Product: + prepareProduct(projectContext, subItem); + break; + case ItemType::Project: + copyProperties(projectItem, subItem); + handleProject(subItem, + Set<QString>(referencedFilePaths) << subItem->file()->filePath()); + break; + default: + break; + } + } +} + +QList<Item *> ProductsCollector::Private::multiplexProductItem(ProductContext &dummyContext, + Item *productItem) +{ + // Overriding the product item properties must be done here already, because multiplexing + // properties might depend on product properties. + const QString &nameKey = StringConstants::nameProperty(); + QString productName = evaluator.stringValue(productItem, nameKey); + if (productName.isEmpty()) { + productName = FileInfo::completeBaseName(productItem->file()->filePath()); + productItem->setProperty(nameKey, VariantValue::create(productName)); + } + productItem->overrideProperties(parameters.overriddenValuesTree(), + StringConstants::productsOverridePrefix() + productName, + parameters, logger); + dummyContext.item = productItem; + TempBaseModuleAttacher tbma(this, dummyContext); + return multiplexer.multiplex(productName, productItem, tbma.tempBaseModuleItem(), + [&] { tbma.drop(); }); +} + +void ProductsCollector::Private::prepareProduct(ProjectContext &projectContext, Item *productItem) +{ + AccumulatingTimer timer(parameters.logElapsedTime() ? &elapsedTimePrepareProducts : nullptr); + topLevelProject.checkCancelation(parameters); + qCDebug(lcModuleLoader) << "prepareProduct" << productItem->file()->filePath(); + + ProductContext productContext; + productContext.item = productItem; + productContext.project = &projectContext; + + // Retrieve name, profile and multiplex id. + productContext.name = evaluator.stringValue(productItem, StringConstants::nameProperty()); + QBS_CHECK(!productContext.name.isEmpty()); + const ItemValueConstPtr qbsItemValue = productItem->itemProperty(StringConstants::qbsModule()); + if (qbsItemValue && qbsItemValue->item()->hasProperty(StringConstants::profileProperty())) { + TempBaseModuleAttacher tbma(this, productContext); + productContext.profileName = evaluator.stringValue( + tbma.tempBaseModuleItem(), StringConstants::profileProperty(), QString()); + } else { + productContext.profileName = parameters.topLevelProfile(); + } + productContext.multiplexConfigurationId = evaluator.stringValue( + productItem, StringConstants::multiplexConfigurationIdProperty()); + QBS_CHECK(!productContext.profileName.isEmpty()); + + // Set up full module property map based on the profile. + const auto it = topLevelProject.profileConfigs.constFind(productContext.profileName); + QVariantMap flatConfig; + 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()); + productContext.handleError(error); + return; + } + flatConfig = SetupProjectParameters::expandedBuildConfiguration( + profile, parameters.configurationName()); + topLevelProject.profileConfigs.insert(productContext.profileName, flatConfig); + } else { + flatConfig = it.value().toMap(); + } + productContext.profileModuleProperties = SetupProjectParameters::finalBuildConfigurationTree( + flatConfig, {}); + productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree( + flatConfig, parameters.overriddenValues()); + initProductProperties(productContext); + + // Set up product scope. This is mainly for using the "product" and "project" + // variables in some contexts. + ItemValuePtr itemValue = ItemValue::create(productItem); + productContext.scope = Item::create(productItem->pool(), ItemType::Scope); + productContext.scope->setProperty(StringConstants::productVar(), itemValue); + productContext.scope->setFile(productItem->file()); + productContext.scope->setScope(productContext.project->scope); + + const bool hasExportItems = mergeExportItems(productContext); + + setScopeForDescendants(productItem, productContext.scope); + + projectContext.products.push_back(productContext); + + if (!hasExportItems || getShadowProductInfo(productContext).first) + return; + + // This "shadow product" exists only to pull in a dependency on the actual product + // and nothing else, thus providing us with the pure environment that we need to + // evaluate the product's exported properties in isolation in the project resolver. + Item * const importer = Item::create(productItem->pool(), ItemType::Product); + importer->setProperty(QStringLiteral("name"), + VariantValue::create(StringConstants::shadowProductPrefix() + + productContext.name)); + importer->setFile(productItem->file()); + importer->setLocation(productItem->location()); + importer->setScope(projectContext.scope); + importer->setupForBuiltinType(parameters.deprecationWarningMode(), logger); + Item * const dependsItem = Item::create(productItem->pool(), ItemType::Depends); + dependsItem->setProperty(QStringLiteral("name"), VariantValue::create(productContext.name)); + dependsItem->setProperty(QStringLiteral("required"), VariantValue::create(false)); + dependsItem->setFile(importer->file()); + dependsItem->setLocation(importer->location()); + dependsItem->setupForBuiltinType(parameters.deprecationWarningMode(), logger); + Item::addChild(importer, dependsItem); + Item::addChild(productItem, importer); + prepareProduct(projectContext, importer); +} + +void ProductsCollector::Private::handleSubProject( + ProjectContext &projectContext, Item *projectItem, const Set<QString> &referencedFilePaths) +{ + qCDebug(lcModuleLoader) << "handleSubProject" << projectItem->file()->filePath(); + + Item * const propertiesItem = projectItem->child(ItemType::PropertiesInSubProject); + if (!topLevelProject.checkItemCondition(projectItem, evaluator)) + return; + if (propertiesItem) { + propertiesItem->setScope(projectItem); + if (!topLevelProject.checkItemCondition(propertiesItem, evaluator)) + return; + } + + Item *loadedItem = nullptr; + QString subProjectFilePath; + try { + const QString projectFileDirPath = FileInfo::path(projectItem->file()->filePath()); + const QString relativeFilePath + = evaluator.stringValue(projectItem, StringConstants::filePathProperty()); + subProjectFilePath = FileInfo::resolvePath(projectFileDirPath, relativeFilePath); + if (referencedFilePaths.contains(subProjectFilePath)) + throw ErrorInfo(Tr::tr("Cycle detected while loading subproject file '%1'.") + .arg(relativeFilePath), projectItem->location()); + loadedItem = itemReader.setupItemFromFile(subProjectFilePath, projectItem->location(), + evaluator); + } catch (const ErrorInfo &error) { + if (parameters.productErrorMode() == ErrorHandlingMode::Strict) + throw; + logger.printWarning(error); + return; + } + + loadedItem = itemReader.wrapInProjectIfNecessary(loadedItem, parameters); + const bool inheritProperties = evaluator.boolValue( + projectItem, StringConstants::inheritPropertiesProperty()); + + if (inheritProperties) + copyProperties(projectItem->parent(), loadedItem); + if (propertiesItem) { + const Item::PropertyMap &overriddenProperties = propertiesItem->properties(); + for (auto it = overriddenProperties.begin(); it != overriddenProperties.end(); ++it) + loadedItem->setProperty(it.key(), it.value()); + } + + Item::addChild(projectItem, loadedItem); + projectItem->setScope(projectContext.scope); + handleProject(loadedItem, Set<QString>(referencedFilePaths) << subProjectFilePath); + +} + +void ProductsCollector::Private::copyProperties(const Item *sourceProject, Item *targetProject) +{ + if (!sourceProject) + return; + const QList<PropertyDeclaration> builtinProjectProperties + = BuiltinDeclarations::instance().declarationsForType(ItemType::Project).properties(); + Set<QString> builtinProjectPropertyNames; + for (const PropertyDeclaration &p : builtinProjectProperties) + builtinProjectPropertyNames << p.name(); + + for (auto it = sourceProject->propertyDeclarations().begin(); + it != sourceProject->propertyDeclarations().end(); ++it) { + + // We must not inherit built-in properties such as "name", + // but there are exceptions. + if (it.key() == StringConstants::qbsSearchPathsProperty() + || it.key() == StringConstants::profileProperty() + || it.key() == StringConstants::buildDirectoryProperty() + || it.key() == StringConstants::sourceDirectoryProperty() + || it.key() == StringConstants::minimumQbsVersionProperty()) { + const JSSourceValueConstPtr &v = targetProject->sourceProperty(it.key()); + QBS_ASSERT(v, continue); + if (v->sourceCode() == StringConstants::undefinedValue()) + sourceProject->copyProperty(it.key(), targetProject); + continue; + } + + if (builtinProjectPropertyNames.contains(it.key())) + continue; + + if (targetProject->hasOwnProperty(it.key())) + continue; // Ignore stuff the target project already has. + + targetProject->setPropertyDeclaration(it.key(), it.value()); + sourceProject->copyProperty(it.key(), targetProject); + } +} + +QList<Item *> ProductsCollector::Private::loadReferencedFile( + const QString &relativePath, const CodeLocation &referencingLocation, + const Set<QString> &referencedFilePaths, ProductContext &dummyContext) +{ + QString absReferencePath = FileInfo::resolvePath(FileInfo::path(referencingLocation.filePath()), + relativePath); + if (FileInfo(absReferencePath).isDir()) { + QString qbsFilePath; + + QDirIterator dit(absReferencePath, StringConstants::qbsFileWildcards()); + while (dit.hasNext()) { + if (!qbsFilePath.isEmpty()) { + throw ErrorInfo(Tr::tr("Referenced directory '%1' contains more than one " + "qbs file.").arg(absReferencePath), referencingLocation); + } + qbsFilePath = dit.next(); + } + if (qbsFilePath.isEmpty()) { + throw ErrorInfo(Tr::tr("Referenced directory '%1' does not contain a qbs file.") + .arg(absReferencePath), referencingLocation); + } + absReferencePath = qbsFilePath; + } + if (referencedFilePaths.contains(absReferencePath)) + throw ErrorInfo(Tr::tr("Cycle detected while referencing file '%1'.").arg(relativePath), + referencingLocation); + Item * const subItem = itemReader.setupItemFromFile(absReferencePath, referencingLocation, + evaluator); + if (subItem->type() != ItemType::Project && subItem->type() != ItemType::Product) { + ErrorInfo error(Tr::tr("Item type should be 'Product' or 'Project', but is '%1'.") + .arg(subItem->typeName())); + error.append(Tr::tr("Item is defined here."), subItem->location()); + error.append(Tr::tr("File is referenced here."), referencingLocation); + throw error; + } + subItem->setScope(dummyContext.project->scope); + subItem->setParent(dummyContext.project->item); + QList<Item *> loadedItems; + loadedItems << subItem; + if (subItem->type() == ItemType::Product) { + localProfiles.collectProfilesFromItems(subItem, dummyContext.project->scope); + loadedItems << multiplexProductItem(dummyContext, subItem); + } + return loadedItems; +} + +static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value) +{ + if (value->type() == Value::ItemValueType) { + const ItemValueConstPtr itemValue = std::static_pointer_cast<ItemValue>(value); + const Item * const valueItem = itemValue->item(); + Item * const subItem = dst->itemProperty(name, itemValue)->item(); + for (auto it = valueItem->properties().begin(); it != valueItem->properties().end(); ++it) + mergeProperty(subItem, it.key(), it.value()); + return; + } + + // If the property already exists, set up the base value. + if (value->type() == Value::JSSourceValueType) { + const auto jsValue = static_cast<JSSourceValue *>(value.get()); + if (jsValue->isBuiltinDefaultValue()) + return; + const ValuePtr baseValue = dst->property(name); + if (baseValue) { + QBS_CHECK(baseValue->type() == Value::JSSourceValueType); + const JSSourceValuePtr jsBaseValue = std::static_pointer_cast<JSSourceValue>( + baseValue->clone()); + jsValue->setBaseValue(jsBaseValue); + std::vector<JSSourceValue::Alternative> alternatives = jsValue->alternatives(); + jsValue->clearAlternatives(); + for (JSSourceValue::Alternative &a : alternatives) { + a.value->setBaseValue(jsBaseValue); + jsValue->addAlternative(a); + } + } + } + dst->setProperty(name, value); +} + +bool ProductsCollector::Private::mergeExportItems(ProductContext &productContext) +{ + std::vector<Item *> exportItems; + QList<Item *> children = productContext.item->children(); + + const auto isExport = [](Item *item) { return item->type() == ItemType::Export; }; + std::copy_if(children.cbegin(), children.cend(), std::back_inserter(exportItems), isExport); + qbs::Internal::removeIf(children, isExport); + + // Note that we do not return if there are no Export items: The "merged" item becomes the + // "product module", which always needs to exist, regardless of whether the product sources + // actually contain an Export item or not. + if (!exportItems.empty()) + productContext.item->setChildren(children); + + Item *merged = Item::create(productContext.item->pool(), ItemType::Export); + const QString &nameKey = StringConstants::nameProperty(); + const ValuePtr nameValue = VariantValue::create(productContext.name); + merged->setProperty(nameKey, nameValue); + Set<FileContextConstPtr> filesWithExportItem; + QVariantMap defaultParameters; + for (Item * const exportItem : exportItems) { + topLevelProject.checkCancelation(parameters); + if (Q_UNLIKELY(filesWithExportItem.contains(exportItem->file()))) + throw ErrorInfo(Tr::tr("Multiple Export items in one product are prohibited."), + exportItem->location()); + exportItem->setProperty(nameKey, nameValue); + if (!checkExportItemCondition(exportItem, productContext)) + continue; + filesWithExportItem += exportItem->file(); + for (Item * const child : exportItem->children()) { + if (child->type() == ItemType::Parameters) { + adjustParametersScopes(child, child); + mergeParameters(defaultParameters, + getJsVariant(evaluator.engine()->context(), + evaluator.scriptValue(child)).toMap()); + } else { + Item::addChild(merged, child); + } + } + const Item::PropertyDeclarationMap &decls = exportItem->propertyDeclarations(); + for (auto it = decls.constBegin(); it != decls.constEnd(); ++it) { + const PropertyDeclaration &newDecl = it.value(); + const PropertyDeclaration &existingDecl = merged->propertyDeclaration(it.key()); + if (existingDecl.isValid() && existingDecl.type() != newDecl.type()) { + ErrorInfo error(Tr::tr("Export item in inherited item redeclares property " + "'%1' with different type.").arg(it.key()), exportItem->location()); + handlePropertyError(error, parameters, logger); + } + merged->setPropertyDeclaration(newDecl.name(), newDecl); + } + for (QMap<QString, ValuePtr>::const_iterator it = exportItem->properties().constBegin(); + it != exportItem->properties().constEnd(); ++it) { + mergeProperty(merged, it.key(), it.value()); + } + } + merged->setFile(exportItems.empty() + ? productContext.item->file() : exportItems.back()->file()); + merged->setLocation(exportItems.empty() + ? productContext.item->location() : exportItems.back()->location()); + Item::addChild(productContext.item, merged); + merged->setupForBuiltinType(parameters.deprecationWarningMode(), logger); + + productContext.mergedExportItem = merged; + productContext.defaultParameters = defaultParameters; + return !exportItems.empty(); + +} + +// TODO: This seems dubious. Can we merge the conditions instead? +bool ProductsCollector::Private::checkExportItemCondition(Item *exportItem, + const ProductContext &product) +{ + class ScopeHandler { + public: + ScopeHandler(Item *exportItem, const ProductContext &productContext, Item **cachedScopeItem) + : m_exportItem(exportItem) + { + if (!*cachedScopeItem) + *cachedScopeItem = Item::create(exportItem->pool(), ItemType::Scope); + Item * const scope = *cachedScopeItem; + QBS_CHECK(productContext.item->file()); + scope->setFile(productContext.item->file()); + scope->setScope(productContext.item); + productContext.project->scope->copyProperty(StringConstants::projectVar(), scope); + productContext.scope->copyProperty(StringConstants::productVar(), scope); + QBS_CHECK(!exportItem->scope()); + exportItem->setScope(scope); + } + ~ScopeHandler() { m_exportItem->setScope(nullptr); } + + private: + Item * const m_exportItem; + } scopeHandler(exportItem, product, &tempScopeItem); + return topLevelProject.checkItemCondition(exportItem, evaluator); +} + +void ProductsCollector::Private::initProductProperties(const ProductContext &product) +{ + QString buildDir = ResolvedProduct::deriveBuildDirectoryName(product.name, + product.multiplexConfigurationId); + buildDir = FileInfo::resolvePath(product.project->topLevelProject->buildDirectory, buildDir); + product.item->setProperty(StringConstants::buildDirectoryProperty(), + VariantValue::create(buildDir)); + const QString sourceDir = QFileInfo(product.item->file()->filePath()).absolutePath(); + product.item->setProperty(StringConstants::sourceDirectoryProperty(), + VariantValue::create(sourceDir)); +} + +void ProductsCollector::Private::checkProjectNamesInOverrides() +{ + for (const QString &projectNameInOverride : topLevelProject.projectNamesUsedInOverrides) { + if (disabledProjects.contains(projectNameInOverride)) + continue; + 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); + } + } +} + +void ProductsCollector::Private::collectProductsByName() +{ + for (ProjectContext * const project : topLevelProject.projects) { + for (ProductContext &product : project->products) + topLevelProject.productsByName.insert({product.name, &product}); + } +} + +void ProductsCollector::Private::checkProductNamesInOverrides() +{ + for (const QString &productNameInOverride : topLevelProject.productNamesUsedInOverrides) { + if (topLevelProject.erroneousProducts.contains(productNameInOverride)) + continue; + 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 + // care not to emit false positives here. + return elem.first == productNameInOverride + || elem.first.startsWith(productNameInOverride + StringConstants::dot()); + })) { + handlePropertyError(Tr::tr("Unknown product '%1' in property override.") + .arg(productNameInOverride), parameters, logger); + } + } +} + +ProductsCollector::Private::TempBaseModuleAttacher::TempBaseModuleAttacher( + ProductsCollector::Private *d, ProductContext &product) + : m_productItem(product.item) +{ + const ValuePtr qbsValue = m_productItem->property(StringConstants::qbsModule()); + + // Cloning is necessary because the original value will get "instantiated" now. + if (qbsValue) + m_origQbsValue = qbsValue->clone(); + + m_tempBaseModule = d->dependenciesResolver.loadBaseModule(product, m_productItem); +} + +void ProductsCollector::Private::TempBaseModuleAttacher::drop() +{ + if (!m_tempBaseModule) + return; + + // "Unload" the qbs module again. + if (m_origQbsValue) + m_productItem->setProperty(StringConstants::qbsModule(), m_origQbsValue); + else + m_productItem->removeProperty(StringConstants::qbsModule()); + m_productItem->removeModules(); + m_tempBaseModule = nullptr; +} + +} // namespace qbs::Internal diff --git a/src/lib/corelib/loader/productscollector.h b/src/lib/corelib/loader/productscollector.h new file mode 100644 index 000000000..e1f955e6d --- /dev/null +++ b/src/lib/corelib/loader/productscollector.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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> + +namespace qbs { +class SetupProjectParameters; +namespace Internal { +class DependenciesResolver; +class Evaluator; +class Item; +class ItemReader; +class LocalProfiles; +class Logger; +class ProbesResolver; +class ProductItemMultiplexer; +class TopLevelProjectContext; + +// Traverses the root project item and fills the TopLevelProjectContext with all the +// product and sub-project information, including those coming from referenced files. +class ProductsCollector +{ +public: + ProductsCollector(const SetupProjectParameters ¶meters, + TopLevelProjectContext &topLevelProject, + DependenciesResolver &dependenciesResolver, + Evaluator &evaluator, + ItemReader &itemReader, + ProbesResolver &probesResolver, + LocalProfiles &localProfiles, + ProductItemMultiplexer &multiplexer, + Logger &logger); + ~ProductsCollector(); + + void run(Item *rootProject); + void printProfilingInfo(int indent); + +private: + class Private; + Pimpl<Private> d; +}; + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/loader/projecttreebuilder.cpp b/src/lib/corelib/loader/projecttreebuilder.cpp index a03195318..83288ba5e 100644 --- a/src/lib/corelib/loader/projecttreebuilder.cpp +++ b/src/lib/corelib/loader/projecttreebuilder.cpp @@ -47,6 +47,7 @@ #include "modulepropertymerger.h" #include "probesresolver.h" #include "productitemmultiplexer.h" +#include "productscollector.h" #include <language/builtindeclarations.h> #include <language/evaluator.h> @@ -88,7 +89,6 @@ namespace { class TimingData { public: - qint64 prepareProducts = 0; qint64 handleProducts = 0; qint64 propertyChecking = 0; }; @@ -105,39 +105,18 @@ public: Item *loadTopLevelProjectItem(); void checkOverriddenValues(); void collectNameFromOverride(const QString &overrideString); - Item *loadItemFromFile(const QString &filePath, const CodeLocation &referencingLocation); - Item *wrapInProjectIfNecessary(Item *item); - void handleTopLevelProject(Item *projectItem, const Set<QString> &referencedFilePaths); - void handleProject(Item *projectItem, const Set<QString> &referencedFilePaths); - void prepareProduct(ProjectContext &projectContext, Item *productItem); + void handleTopLevelProject(Item *projectItem); void handleNextProduct(); void handleProduct(ProductContext &productContext, Deferral deferral); - void handleSubProject(ProjectContext &projectContext, Item *projectItem, - const Set<QString> &referencedFilePaths); - void initProductProperties(const ProductContext &product); void printProfilingInfo(); - void checkProjectNamesInOverrides(); - void checkProductNamesInOverrides(); - void collectProductsByName(); void handleModuleSetupError(ProductContext &product, const Item::Module &module, const ErrorInfo &error); - bool checkItemCondition(Item *item); - QList<Item *> multiplexProductItem(ProductContext &dummyContext, Item *productItem); - void checkCancelation() const; - QList<Item *> loadReferencedFile(const QString &relativePath, - const CodeLocation &referencingLocation, - const Set<QString> &referencedFilePaths, - ProductContext &dummyContext); - void copyProperties(const Item *sourceProject, Item *targetProject); - bool mergeExportItems(ProductContext &productContext); - bool checkExportItemCondition(Item *exportItem, const ProductContext &product); void resolveProbes(ProductContext &product, Item *item); const SetupProjectParameters ¶meters; ItemPool &itemPool; Evaluator &evaluator; Logger &logger; - ProgressObserver *progressObserver = nullptr; TimingData timingData; ItemReader reader{parameters, logger}; ProbesResolver probesResolver{parameters, evaluator, logger}; @@ -150,31 +129,12 @@ public: LocalProfiles localProfiles{parameters, evaluator, logger}; DependenciesResolver dependenciesResolver{parameters, itemPool, evaluator, reader, probesResolver, moduleInstantiator, logger}; + ProductsCollector productsCollector{parameters, topLevelProject, dependenciesResolver, + evaluator, reader, probesResolver, localProfiles, multiplexer, logger}; FileTime lastResolveTime; QVariantMap storedProfiles; - Settings settings{parameters.settingsDirectory()}; - Set<QString> projectNamesUsedInOverrides; - Set<QString> productNamesUsedInOverrides; - Set<QString> disabledProjects; TopLevelProjectContext topLevelProject; - - Version qbsVersion; - Item *tempScopeItem = nullptr; - -private: - class TempBaseModuleAttacher { - public: - TempBaseModuleAttacher(Private *d, ProductContext &product); - ~TempBaseModuleAttacher() { drop(); } - void drop(); - Item *tempBaseModuleItem() const { return m_tempBaseModule; } - - private: - Item * const m_productItem; - ValuePtr m_origQbsValue; - Item *m_tempBaseModule = nullptr; - }; }; ProjectTreeBuilder::ProjectTreeBuilder(const SetupProjectParameters ¶meters, ItemPool &itemPool, @@ -184,7 +144,7 @@ ProjectTreeBuilder::~ProjectTreeBuilder() = default; void ProjectTreeBuilder::setProgressObserver(ProgressObserver *progressObserver) { - d->progressObserver = progressObserver; + d->topLevelProject.progressObserver = progressObserver; } void ProjectTreeBuilder::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes) @@ -223,7 +183,7 @@ ProjectTreeBuilder::Result ProjectTreeBuilder::load() Result result; d->topLevelProject.profileConfigs = d->storedProfiles; result.root = d->loadTopLevelProjectItem(); - d->handleTopLevelProject(result.root, {QDir::cleanPath(d->parameters.projectFilePath())}); + d->handleTopLevelProject(result.root); result.qbsFiles = d->reader.filesRead() - d->dependenciesResolver.tempQbsFiles(); result.productInfos = d->topLevelProject.productInfos; @@ -247,13 +207,13 @@ Item *ProjectTreeBuilder::Private::loadTopLevelProjectItem() .value(StringConstants::projectPrefix()).toMap() .value(StringConstants::qbsSearchPathsProperty()).toStringList(); SearchPathsManager searchPathsManager(reader, topLevelSearchPaths); - Item * const root = loadItemFromFile(parameters.projectFilePath(), CodeLocation()); + Item * const root = reader.setupItemFromFile(parameters.projectFilePath(), {}, evaluator); if (!root) return {}; switch (root->type()) { case ItemType::Product: - return wrapInProjectIfNecessary(root); + return reader.wrapInProjectIfNecessary(root, parameters); case ItemType::Project: return root; default: @@ -309,37 +269,18 @@ void ProjectTreeBuilder::Private::collectNameFromOverride(const QString &overrid }; const QString &projectName = extract(StringConstants::projectsOverridePrefix(), overrideString); if (!projectName.isEmpty()) { - projectNamesUsedInOverrides.insert(projectName); + topLevelProject.projectNamesUsedInOverrides.insert(projectName); return; } const QString &productName = extract(StringConstants::productsOverridePrefix(), overrideString); if (!productName.isEmpty()) { - productNamesUsedInOverrides.insert(productName.left( + topLevelProject.productNamesUsedInOverrides.insert(productName.left( productName.indexOf(StringConstants::dot()))); return; } } -Item *ProjectTreeBuilder::Private::loadItemFromFile(const QString &filePath, - const CodeLocation &referencingLocation) -{ - return reader.setupItemFromFile(filePath, referencingLocation, evaluator); -} - -Item *ProjectTreeBuilder::Private::wrapInProjectIfNecessary(Item *item) -{ - if (item->type() == ItemType::Project) - return item; - Item *prj = Item::create(item->pool(), ItemType::Project); - Item::addChild(prj, item); - prj->setFile(item->file()); - prj->setLocation(item->location()); - prj->setupForBuiltinType(parameters.deprecationWarningMode(), logger); - return prj; -} - -void ProjectTreeBuilder::Private::handleTopLevelProject(Item *projectItem, - const Set<QString> &referencedFilePaths) +void ProjectTreeBuilder::Private::handleTopLevelProject(Item *projectItem) { topLevelProject.buildDirectory = TopLevelProject::deriveBuildDirectory( parameters.buildRoot(), @@ -351,12 +292,9 @@ void ProjectTreeBuilder::Private::handleTopLevelProject(Item *projectItem, VariantValue::create(topLevelProject.buildDirectory)); projectItem->setProperty(StringConstants::profileProperty(), VariantValue::create(parameters.topLevelProfile())); - handleProject(projectItem, referencedFilePaths); - checkProjectNamesInOverrides(); - collectProductsByName(); - checkProductNamesInOverrides(); + productsCollector.run(projectItem); - for (ProjectContext * const projectContext : std::as_const(topLevelProject.projects)) { + for (ProjectContext * const projectContext : topLevelProject.projects) { for (ProductContext &productContext : projectContext->products) topLevelProject.productsToHandle.emplace_back(&productContext, -1); } @@ -369,216 +307,6 @@ void ProjectTreeBuilder::Private::handleTopLevelProject(Item *projectItem, checkPropertyDeclarations(projectItem, topLevelProject.disabledItems, parameters, logger); } -void ProjectTreeBuilder::Private::handleProject(Item *projectItem, - const Set<QString> &referencedFilePaths) -{ - auto p = std::make_unique<ProjectContext>(); - auto &projectContext = *p; - projectContext.topLevelProject = &topLevelProject; - ItemValuePtr itemValue = ItemValue::create(projectItem); - projectContext.scope = Item::create(&itemPool, ItemType::Scope); - projectContext.scope->setFile(projectItem->file()); - projectContext.scope->setProperty(StringConstants::projectVar(), itemValue); - ProductContext dummyProductContext; - dummyProductContext.project = &projectContext; - dummyProductContext.moduleProperties = parameters.finalBuildConfigurationTree(); - dummyProductContext.profileModuleProperties = dummyProductContext.moduleProperties; - dummyProductContext.profileName = parameters.topLevelProfile(); - dependenciesResolver.loadBaseModule(dummyProductContext, projectItem); - - projectItem->overrideProperties(parameters.overriddenValuesTree(), - StringConstants::projectPrefix(), parameters, logger); - projectContext.name = evaluator.stringValue(projectItem, StringConstants::nameProperty()); - if (projectContext.name.isEmpty()) { - projectContext.name = FileInfo::baseName(projectItem->location().filePath()); - projectItem->setProperty(StringConstants::nameProperty(), - VariantValue::create(projectContext.name)); - } - projectItem->overrideProperties(parameters.overriddenValuesTree(), - StringConstants::projectsOverridePrefix() + projectContext.name, - parameters, logger); - if (!checkItemCondition(projectItem)) { - disabledProjects.insert(projectContext.name); - return; - } - topLevelProject.projects.push_back(p.release()); - SearchPathsManager searchPathsManager(reader, reader.readExtraSearchPaths(projectItem, evaluator) - << projectItem->file()->dirPath()); - projectContext.searchPathsStack = reader.extraSearchPathsStack(); - projectContext.item = projectItem; - - const QString minVersionStr - = evaluator.stringValue(projectItem, StringConstants::minimumQbsVersionProperty(), - QStringLiteral("1.3.0")); - const Version minVersion = Version::fromString(minVersionStr); - if (!minVersion.isValid()) { - throw ErrorInfo(Tr::tr("The value '%1' of Project.minimumQbsVersion " - "is not a valid version string.") - .arg(minVersionStr), projectItem->location()); - } - if (!qbsVersion.isValid()) - qbsVersion = Version::fromString(QLatin1String(QBS_VERSION)); - if (qbsVersion < minVersion) { - throw ErrorInfo(Tr::tr("The project requires at least qbs version %1, but " - "this is qbs version %2.").arg(minVersion.toString(), - qbsVersion.toString())); - } - - for (Item * const child : projectItem->children()) - child->setScope(projectContext.scope); - - resolveProbes(dummyProductContext, projectItem); - projectContext.topLevelProject->probes << dummyProductContext.info.probes; - - localProfiles.collectProfilesFromItems(projectItem, projectContext.scope); - - QList<Item *> multiplexedProducts; - for (Item * const child : projectItem->children()) { - if (child->type() == ItemType::Product) - multiplexedProducts << multiplexProductItem(dummyProductContext, child); - } - for (Item * const additionalProductItem : std::as_const(multiplexedProducts)) - Item::addChild(projectItem, additionalProductItem); - - const QList<Item *> originalChildren = projectItem->children(); - for (Item * const child : originalChildren) { - switch (child->type()) { - case ItemType::Product: - prepareProduct(projectContext, child); - break; - case ItemType::SubProject: - handleSubProject(projectContext, child, referencedFilePaths); - break; - case ItemType::Project: - copyProperties(projectItem, child); - handleProject(child, referencedFilePaths); - break; - default: - break; - } - } - - const QStringList refs = evaluator.stringListValue( - projectItem, StringConstants::referencesProperty()); - const CodeLocation referencingLocation - = projectItem->property(StringConstants::referencesProperty())->location(); - QList<Item *> additionalProjectChildren; - for (const QString &filePath : refs) { - try { - additionalProjectChildren << loadReferencedFile( - filePath, referencingLocation, referencedFilePaths, dummyProductContext); - } catch (const ErrorInfo &error) { - if (parameters.productErrorMode() == ErrorHandlingMode::Strict) - throw; - logger.printWarning(error); - } - } - for (Item * const subItem : std::as_const(additionalProjectChildren)) { - Item::addChild(projectContext.item, subItem); - switch (subItem->type()) { - case ItemType::Product: - prepareProduct(projectContext, subItem); - break; - case ItemType::Project: - copyProperties(projectItem, subItem); - handleProject(subItem, - Set<QString>(referencedFilePaths) << subItem->file()->filePath()); - break; - default: - break; - } - } - -} - -void ProjectTreeBuilder::Private::prepareProduct(ProjectContext &projectContext, Item *productItem) -{ - AccumulatingTimer timer(parameters.logElapsedTime() - ? &timingData.prepareProducts : nullptr); - checkCancelation(); - qCDebug(lcModuleLoader) << "prepareProduct" << productItem->file()->filePath(); - - ProductContext productContext; - productContext.item = productItem; - productContext.project = &projectContext; - - // Retrieve name, profile and multiplex id. - productContext.name = evaluator.stringValue(productItem, StringConstants::nameProperty()); - QBS_CHECK(!productContext.name.isEmpty()); - const ItemValueConstPtr qbsItemValue = productItem->itemProperty(StringConstants::qbsModule()); - if (qbsItemValue && qbsItemValue->item()->hasProperty(StringConstants::profileProperty())) { - TempBaseModuleAttacher tbma(this, productContext); - productContext.profileName = evaluator.stringValue( - tbma.tempBaseModuleItem(), StringConstants::profileProperty(), QString()); - } else { - productContext.profileName = parameters.topLevelProfile(); - } - productContext.multiplexConfigurationId = evaluator.stringValue( - productItem, StringConstants::multiplexConfigurationIdProperty()); - QBS_CHECK(!productContext.profileName.isEmpty()); - - // Set up full module property map based on the profile. - const auto it = topLevelProject.profileConfigs.constFind(productContext.profileName); - QVariantMap flatConfig; - 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()); - productContext.handleError(error); - return; - } - flatConfig = SetupProjectParameters::expandedBuildConfiguration( - profile, parameters.configurationName()); - topLevelProject.profileConfigs.insert(productContext.profileName, flatConfig); - } else { - flatConfig = it.value().toMap(); - } - productContext.profileModuleProperties = SetupProjectParameters::finalBuildConfigurationTree( - flatConfig, {}); - productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree( - flatConfig, parameters.overriddenValues()); - initProductProperties(productContext); - - // Set up product scope. This is mainly for using the "product" and "project" - // variables in some contexts. - ItemValuePtr itemValue = ItemValue::create(productItem); - productContext.scope = Item::create(&itemPool, ItemType::Scope); - productContext.scope->setProperty(StringConstants::productVar(), itemValue); - productContext.scope->setFile(productItem->file()); - productContext.scope->setScope(productContext.project->scope); - - const bool hasExportItems = mergeExportItems(productContext); - - setScopeForDescendants(productItem, productContext.scope); - - projectContext.products.push_back(productContext); - - if (!hasExportItems || getShadowProductInfo(productContext).first) - return; - - // This "shadow product" exists only to pull in a dependency on the actual product - // and nothing else, thus providing us with the pure environment that we need to - // evaluate the product's exported properties in isolation in the project resolver. - Item * const importer = Item::create(productItem->pool(), ItemType::Product); - importer->setProperty(QStringLiteral("name"), - VariantValue::create(StringConstants::shadowProductPrefix() - + productContext.name)); - importer->setFile(productItem->file()); - importer->setLocation(productItem->location()); - importer->setScope(projectContext.scope); - importer->setupForBuiltinType(parameters.deprecationWarningMode(), logger); - Item * const dependsItem = Item::create(productItem->pool(), ItemType::Depends); - dependsItem->setProperty(QStringLiteral("name"), VariantValue::create(productContext.name)); - dependsItem->setProperty(QStringLiteral("required"), VariantValue::create(false)); - dependsItem->setFile(importer->file()); - dependsItem->setLocation(importer->location()); - dependsItem->setupForBuiltinType(parameters.deprecationWarningMode(), logger); - Item::addChild(importer, dependsItem); - Item::addChild(productItem, importer); - prepareProduct(projectContext, importer); -} - void ProjectTreeBuilder::Private::handleNextProduct() { auto [product, queueSizeOnInsert] = topLevelProject.productsToHandle.front(); @@ -613,7 +341,7 @@ void ProjectTreeBuilder::Private::handleNextProduct() void ProjectTreeBuilder::Private::handleProduct(ProductContext &product, Deferral deferral) { - checkCancelation(); + topLevelProject.checkCancelation(parameters); AccumulatingTimer timer(parameters.logElapsedTime() ? &timingData.handleProducts : nullptr); if (product.info.delayedError.hasError()) @@ -733,7 +461,7 @@ void ProjectTreeBuilder::Private::handleProduct(ProductContext &product, Deferra // Evaluator cache entries that could potentially change due to this will be purged. propertyMerger.doFinalMerge(product.item); - const bool enabled = checkItemCondition(product.item); + const bool enabled = topLevelProject.checkItemCondition(product.item, evaluator); dependenciesResolver.checkDependencyParameterDeclarations(product.item, product.name); groupsHandler.setupGroups(product.item, product.scope); @@ -751,67 +479,6 @@ void ProjectTreeBuilder::Private::handleProduct(ProductContext &product, Deferra topLevelProject.productInfos[product.item] = product.info; } -void ProjectTreeBuilder::Private::handleSubProject( - ProjectContext &projectContext, Item *projectItem, const Set<QString> &referencedFilePaths) -{ - qCDebug(lcModuleLoader) << "handleSubProject" << projectItem->file()->filePath(); - - Item * const propertiesItem = projectItem->child(ItemType::PropertiesInSubProject); - if (!checkItemCondition(projectItem)) - return; - if (propertiesItem) { - propertiesItem->setScope(projectItem); - if (!checkItemCondition(propertiesItem)) - return; - } - - Item *loadedItem = nullptr; - QString subProjectFilePath; - try { - const QString projectFileDirPath = FileInfo::path(projectItem->file()->filePath()); - const QString relativeFilePath - = evaluator.stringValue(projectItem, StringConstants::filePathProperty()); - subProjectFilePath = FileInfo::resolvePath(projectFileDirPath, relativeFilePath); - if (referencedFilePaths.contains(subProjectFilePath)) - throw ErrorInfo(Tr::tr("Cycle detected while loading subproject file '%1'.") - .arg(relativeFilePath), projectItem->location()); - loadedItem = loadItemFromFile(subProjectFilePath, projectItem->location()); - } catch (const ErrorInfo &error) { - if (parameters.productErrorMode() == ErrorHandlingMode::Strict) - throw; - logger.printWarning(error); - return; - } - - loadedItem = wrapInProjectIfNecessary(loadedItem); - const bool inheritProperties = evaluator.boolValue( - projectItem, StringConstants::inheritPropertiesProperty()); - - if (inheritProperties) - copyProperties(projectItem->parent(), loadedItem); - if (propertiesItem) { - const Item::PropertyMap &overriddenProperties = propertiesItem->properties(); - for (auto it = overriddenProperties.begin(); it != overriddenProperties.end(); ++it) - loadedItem->setProperty(it.key(), it.value()); - } - - Item::addChild(projectItem, loadedItem); - projectItem->setScope(projectContext.scope); - handleProject(loadedItem, Set<QString>(referencedFilePaths) << subProjectFilePath); -} - -void ProjectTreeBuilder::Private::initProductProperties(const ProductContext &product) -{ - QString buildDir = ResolvedProduct::deriveBuildDirectoryName(product.name, - product.multiplexConfigurationId); - buildDir = FileInfo::resolvePath(product.project->topLevelProject->buildDirectory, buildDir); - product.item->setProperty(StringConstants::buildDirectoryProperty(), - VariantValue::create(buildDir)); - const QString sourceDir = QFileInfo(product.item->file()->filePath()).absolutePath(); - product.item->setProperty(StringConstants::sourceDirectoryProperty(), - VariantValue::create(sourceDir)); -} - void ProjectTreeBuilder::Private::printProfilingInfo() { if (!parameters.logElapsedTime()) @@ -819,9 +486,7 @@ void ProjectTreeBuilder::Private::printProfilingInfo() logger.qbsLog(LoggerInfo, true) << " " << Tr::tr("Project file loading and parsing took %1.") .arg(elapsedTimeString(reader.elapsedTime())); - logger.qbsLog(LoggerInfo, true) << " " - << Tr::tr("Preparing products took %1.") - .arg(elapsedTimeString(timingData.prepareProducts)); + productsCollector.printProfilingInfo(2); logger.qbsLog(LoggerInfo, true) << " " << Tr::tr("Handling products took %1.") .arg(elapsedTimeString(timingData.handleProducts)); @@ -835,46 +500,6 @@ void ProjectTreeBuilder::Private::printProfilingInfo() .arg(elapsedTimeString(timingData.propertyChecking)); } -void ProjectTreeBuilder::Private::checkProjectNamesInOverrides() -{ - for (const QString &projectNameInOverride : projectNamesUsedInOverrides) { - if (disabledProjects.contains(projectNameInOverride)) - continue; - 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); - } - } -} - -void ProjectTreeBuilder::Private::checkProductNamesInOverrides() -{ - for (const QString &productNameInOverride : productNamesUsedInOverrides) { - if (topLevelProject.erroneousProducts.contains(productNameInOverride)) - continue; - 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 - // care not to emit false positives here. - return elem.first == productNameInOverride - || elem.first.startsWith(productNameInOverride + StringConstants::dot()); - })) { - handlePropertyError(Tr::tr("Unknown product '%1' in property override.") - .arg(productNameInOverride), parameters, logger); - } - } -} - -void ProjectTreeBuilder::Private::collectProductsByName() -{ - for (ProjectContext * const project : topLevelProject.projects) { - for (ProductContext &product : project->products) - topLevelProject.productsByName.insert({product.name, &product}); - } -} - void ProjectTreeBuilder::Private::handleModuleSetupError( ProductContext &product, const Item::Module &module, const ErrorInfo &error) { @@ -889,282 +514,9 @@ void ProjectTreeBuilder::Private::handleModuleSetupError( } } -bool ProjectTreeBuilder::Private::checkItemCondition(Item *item) -{ - return topLevelProject.checkItemCondition(item, evaluator); -} - -QList<Item *> ProjectTreeBuilder::Private::multiplexProductItem(ProductContext &dummyContext, - Item *productItem) -{ - // Overriding the product item properties must be done here already, because multiplexing - // properties might depend on product properties. - const QString &nameKey = StringConstants::nameProperty(); - QString productName = evaluator.stringValue(productItem, nameKey); - if (productName.isEmpty()) { - productName = FileInfo::completeBaseName(productItem->file()->filePath()); - productItem->setProperty(nameKey, VariantValue::create(productName)); - } - productItem->overrideProperties(parameters.overriddenValuesTree(), - StringConstants::productsOverridePrefix() + productName, - parameters, logger); - dummyContext.item = productItem; - TempBaseModuleAttacher tbma(this, dummyContext); - return multiplexer.multiplex(productName, productItem, tbma.tempBaseModuleItem(), - [&] { tbma.drop(); }); -} - -void ProjectTreeBuilder::Private::checkCancelation() const -{ - if (progressObserver && progressObserver->canceled()) { - throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.") - .arg(TopLevelProject::deriveId( - parameters.finalBuildConfigurationTree()))); - } -} - -QList<Item *> ProjectTreeBuilder::Private::loadReferencedFile( - const QString &relativePath, const CodeLocation &referencingLocation, - const Set<QString> &referencedFilePaths, ProductContext &dummyContext) -{ - QString absReferencePath = FileInfo::resolvePath(FileInfo::path(referencingLocation.filePath()), - relativePath); - if (FileInfo(absReferencePath).isDir()) { - QString qbsFilePath; - - QDirIterator dit(absReferencePath, StringConstants::qbsFileWildcards()); - while (dit.hasNext()) { - if (!qbsFilePath.isEmpty()) { - throw ErrorInfo(Tr::tr("Referenced directory '%1' contains more than one " - "qbs file.").arg(absReferencePath), referencingLocation); - } - qbsFilePath = dit.next(); - } - if (qbsFilePath.isEmpty()) { - throw ErrorInfo(Tr::tr("Referenced directory '%1' does not contain a qbs file.") - .arg(absReferencePath), referencingLocation); - } - absReferencePath = qbsFilePath; - } - if (referencedFilePaths.contains(absReferencePath)) - throw ErrorInfo(Tr::tr("Cycle detected while referencing file '%1'.").arg(relativePath), - referencingLocation); - Item * const subItem = loadItemFromFile(absReferencePath, referencingLocation); - if (subItem->type() != ItemType::Project && subItem->type() != ItemType::Product) { - ErrorInfo error(Tr::tr("Item type should be 'Product' or 'Project', but is '%1'.") - .arg(subItem->typeName())); - error.append(Tr::tr("Item is defined here."), subItem->location()); - error.append(Tr::tr("File is referenced here."), referencingLocation); - throw error; - } - subItem->setScope(dummyContext.project->scope); - subItem->setParent(dummyContext.project->item); - QList<Item *> loadedItems; - loadedItems << subItem; - if (subItem->type() == ItemType::Product) { - localProfiles.collectProfilesFromItems(subItem, dummyContext.project->scope); - loadedItems << multiplexProductItem(dummyContext, subItem); - } - return loadedItems; -} - -void ProjectTreeBuilder::Private::copyProperties(const Item *sourceProject, Item *targetProject) -{ - if (!sourceProject) - return; - const QList<PropertyDeclaration> builtinProjectProperties - = BuiltinDeclarations::instance().declarationsForType(ItemType::Project).properties(); - Set<QString> builtinProjectPropertyNames; - for (const PropertyDeclaration &p : builtinProjectProperties) - builtinProjectPropertyNames << p.name(); - - for (auto it = sourceProject->propertyDeclarations().begin(); - it != sourceProject->propertyDeclarations().end(); ++it) { - - // We must not inherit built-in properties such as "name", - // but there are exceptions. - if (it.key() == StringConstants::qbsSearchPathsProperty() - || it.key() == StringConstants::profileProperty() - || it.key() == StringConstants::buildDirectoryProperty() - || it.key() == StringConstants::sourceDirectoryProperty() - || it.key() == StringConstants::minimumQbsVersionProperty()) { - const JSSourceValueConstPtr &v = targetProject->sourceProperty(it.key()); - QBS_ASSERT(v, continue); - if (v->sourceCode() == StringConstants::undefinedValue()) - sourceProject->copyProperty(it.key(), targetProject); - continue; - } - - if (builtinProjectPropertyNames.contains(it.key())) - continue; - - if (targetProject->hasOwnProperty(it.key())) - continue; // Ignore stuff the target project already has. - - targetProject->setPropertyDeclaration(it.key(), it.value()); - sourceProject->copyProperty(it.key(), targetProject); - } -} - -static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value) -{ - if (value->type() == Value::ItemValueType) { - const ItemValueConstPtr itemValue = std::static_pointer_cast<ItemValue>(value); - const Item * const valueItem = itemValue->item(); - Item * const subItem = dst->itemProperty(name, itemValue)->item(); - for (auto it = valueItem->properties().begin(); it != valueItem->properties().end(); ++it) - mergeProperty(subItem, it.key(), it.value()); - return; - } - - // If the property already exists, set up the base value. - if (value->type() == Value::JSSourceValueType) { - const auto jsValue = static_cast<JSSourceValue *>(value.get()); - if (jsValue->isBuiltinDefaultValue()) - return; - const ValuePtr baseValue = dst->property(name); - if (baseValue) { - QBS_CHECK(baseValue->type() == Value::JSSourceValueType); - const JSSourceValuePtr jsBaseValue = std::static_pointer_cast<JSSourceValue>( - baseValue->clone()); - jsValue->setBaseValue(jsBaseValue); - std::vector<JSSourceValue::Alternative> alternatives = jsValue->alternatives(); - jsValue->clearAlternatives(); - for (JSSourceValue::Alternative &a : alternatives) { - a.value->setBaseValue(jsBaseValue); - jsValue->addAlternative(a); - } - } - } - dst->setProperty(name, value); -} - -bool ProjectTreeBuilder::Private::mergeExportItems(ProductContext &productContext) -{ - std::vector<Item *> exportItems; - QList<Item *> children = productContext.item->children(); - - const auto isExport = [](Item *item) { return item->type() == ItemType::Export; }; - std::copy_if(children.cbegin(), children.cend(), std::back_inserter(exportItems), isExport); - qbs::Internal::removeIf(children, isExport); - - // Note that we do not return if there are no Export items: The "merged" item becomes the - // "product module", which always needs to exist, regardless of whether the product sources - // actually contain an Export item or not. - if (!exportItems.empty()) - productContext.item->setChildren(children); - - Item *merged = Item::create(productContext.item->pool(), ItemType::Export); - const QString &nameKey = StringConstants::nameProperty(); - const ValuePtr nameValue = VariantValue::create(productContext.name); - merged->setProperty(nameKey, nameValue); - Set<FileContextConstPtr> filesWithExportItem; - QVariantMap defaultParameters; - for (Item * const exportItem : std::as_const(exportItems)) { - checkCancelation(); - if (Q_UNLIKELY(filesWithExportItem.contains(exportItem->file()))) - throw ErrorInfo(Tr::tr("Multiple Export items in one product are prohibited."), - exportItem->location()); - exportItem->setProperty(nameKey, nameValue); - if (!checkExportItemCondition(exportItem, productContext)) - continue; - filesWithExportItem += exportItem->file(); - for (Item * const child : exportItem->children()) { - if (child->type() == ItemType::Parameters) { - adjustParametersScopes(child, child); - mergeParameters(defaultParameters, - getJsVariant(evaluator.engine()->context(), - evaluator.scriptValue(child)).toMap()); - } else { - Item::addChild(merged, child); - } - } - const Item::PropertyDeclarationMap &decls = exportItem->propertyDeclarations(); - for (auto it = decls.constBegin(); it != decls.constEnd(); ++it) { - const PropertyDeclaration &newDecl = it.value(); - const PropertyDeclaration &existingDecl = merged->propertyDeclaration(it.key()); - if (existingDecl.isValid() && existingDecl.type() != newDecl.type()) { - ErrorInfo error(Tr::tr("Export item in inherited item redeclares property " - "'%1' with different type.").arg(it.key()), exportItem->location()); - handlePropertyError(error, parameters, logger); - } - merged->setPropertyDeclaration(newDecl.name(), newDecl); - } - for (QMap<QString, ValuePtr>::const_iterator it = exportItem->properties().constBegin(); - it != exportItem->properties().constEnd(); ++it) { - mergeProperty(merged, it.key(), it.value()); - } - } - merged->setFile(exportItems.empty() - ? productContext.item->file() : exportItems.back()->file()); - merged->setLocation(exportItems.empty() - ? productContext.item->location() : exportItems.back()->location()); - Item::addChild(productContext.item, merged); - merged->setupForBuiltinType(parameters.deprecationWarningMode(), logger); - - productContext.mergedExportItem = merged; - productContext.defaultParameters = defaultParameters; - return !exportItems.empty(); -} - -// TODO: This seems dubious. Can we merge the conditions instead? -bool ProjectTreeBuilder::Private::checkExportItemCondition(Item *exportItem, - const ProductContext &product) -{ - class ScopeHandler { - public: - ScopeHandler(Item *exportItem, const ProductContext &productContext, Item **cachedScopeItem) - : m_exportItem(exportItem) - { - if (!*cachedScopeItem) - *cachedScopeItem = Item::create(exportItem->pool(), ItemType::Scope); - Item * const scope = *cachedScopeItem; - QBS_CHECK(productContext.item->file()); - scope->setFile(productContext.item->file()); - scope->setScope(productContext.item); - productContext.project->scope->copyProperty(StringConstants::projectVar(), scope); - productContext.scope->copyProperty(StringConstants::productVar(), scope); - QBS_CHECK(!exportItem->scope()); - exportItem->setScope(scope); - } - ~ScopeHandler() { m_exportItem->setScope(nullptr); } - - private: - Item * const m_exportItem; - } scopeHandler(exportItem, product, &tempScopeItem); - return checkItemCondition(exportItem); -} - void ProjectTreeBuilder::Private::resolveProbes(ProductContext &product, Item *item) { product.info.probes << probesResolver.resolveProbes({product.name, product.uniqueName()}, item); } -ProjectTreeBuilder::Private::TempBaseModuleAttacher::TempBaseModuleAttacher( - ProjectTreeBuilder::Private *d, ProductContext &product) - : m_productItem(product.item) -{ - const ValuePtr qbsValue = m_productItem->property(StringConstants::qbsModule()); - - // Cloning is necessary because the original value will get "instantiated" now. - if (qbsValue) - m_origQbsValue = qbsValue->clone(); - - m_tempBaseModule = d->dependenciesResolver.loadBaseModule(product, m_productItem); -} - -void ProjectTreeBuilder::Private::TempBaseModuleAttacher::drop() -{ - if (!m_tempBaseModule) - return; - - // "Unload" the qbs module again. - if (m_origQbsValue) - m_productItem->setProperty(StringConstants::qbsModule(), m_origQbsValue); - else - m_productItem->removeProperty(StringConstants::qbsModule()); - m_productItem->removeModules(); - m_tempBaseModule = nullptr; -} - } // namespace qbs::Internal |