aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/corelib/language
diff options
context:
space:
mode:
authorIvan Komissarov <abbapoh@gmail.com>2021-05-16 17:52:17 +0200
committerIvan Komissarov <ABBAPOH@gmail.com>2021-05-19 14:10:37 +0000
commit0bbefbd5dc0ed597b8d1442594a9ab04fadaba60 (patch)
treeaf3ca87a51f25fa7f8d1d34b165efda905aaf922 /src/lib/corelib/language
parentc7a1ec06313f85a9cf45b44f5940a952348d66d2 (diff)
Move module providers code to the separate class
ModuleLoader is too big and more logic will be added to module providers, so it make sense to extract some code to a separate class. Unfortunately, it is hard to break the dependency between types completely - it is tempting to pass ModuleLoader::ProductContext into new class functions. Alternative would be to pass all necessary data via function parameters which will make the code less readable. Change-Id: Ida61192348ef7db89b21f0d58f05e61969e2d01c Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
Diffstat (limited to 'src/lib/corelib/language')
-rw-r--r--src/lib/corelib/language/itemreader.cpp11
-rw-r--r--src/lib/corelib/language/itemreader.h1
-rw-r--r--src/lib/corelib/language/language.pri2
-rw-r--r--src/lib/corelib/language/moduleloader.cpp208
-rw-r--r--src/lib/corelib/language/moduleloader.h19
-rw-r--r--src/lib/corelib/language/moduleproviderloader.cpp269
-rw-r--r--src/lib/corelib/language/moduleproviderloader.h108
7 files changed, 407 insertions, 211 deletions
diff --git a/src/lib/corelib/language/itemreader.cpp b/src/lib/corelib/language/itemreader.cpp
index 727e10560..afa768a06 100644
--- a/src/lib/corelib/language/itemreader.cpp
+++ b/src/lib/corelib/language/itemreader.cpp
@@ -124,6 +124,17 @@ Item *ItemReader::readFile(const QString &filePath)
return m_visitorState->readFile(filePath, allSearchPaths(), m_pool);
}
+Item *ItemReader::readFile(const QString &filePath, const CodeLocation &referencingLocation)
+{
+ try {
+ return readFile(filePath);
+ } catch (const ErrorInfo &e) {
+ if (e.hasLocation())
+ throw;
+ throw ErrorInfo(e.toString(), referencingLocation);
+ }
+}
+
Set<QString> ItemReader::filesRead() const
{
return m_visitorState->filesRead();
diff --git a/src/lib/corelib/language/itemreader.h b/src/lib/corelib/language/itemreader.h
index b1ac6794b..3dc5329d2 100644
--- a/src/lib/corelib/language/itemreader.h
+++ b/src/lib/corelib/language/itemreader.h
@@ -80,6 +80,7 @@ public:
const QStringList &allSearchPaths() const;
Item *readFile(const QString &filePath);
+ Item *readFile(const QString &filePath, const CodeLocation &referencingLocation);
Set<QString> filesRead() const;
diff --git a/src/lib/corelib/language/language.pri b/src/lib/corelib/language/language.pri
index e07a671b9..249093e61 100644
--- a/src/lib/corelib/language/language.pri
+++ b/src/lib/corelib/language/language.pri
@@ -29,6 +29,7 @@ HEADERS += \
$$PWD/moduleloader.h \
$$PWD/modulemerger.h \
$$PWD/moduleproviderinfo.h \
+ $$PWD/moduleproviderloader.h \
$$PWD/preparescriptobserver.h \
$$PWD/projectresolver.h \
$$PWD/property.h \
@@ -63,6 +64,7 @@ SOURCES += \
$$PWD/loader.cpp \
$$PWD/moduleloader.cpp \
$$PWD/modulemerger.cpp \
+ $$PWD/moduleproviderloader.cpp \
$$PWD/preparescriptobserver.cpp \
$$PWD/scriptpropertyobserver.cpp \
$$PWD/projectresolver.cpp \
diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp
index 5603c862b..3680d94f0 100644
--- a/src/lib/corelib/language/moduleloader.cpp
+++ b/src/lib/corelib/language/moduleloader.cpp
@@ -46,6 +46,7 @@
#include "itemreader.h"
#include "language.h"
#include "modulemerger.h"
+#include "moduleproviderloader.h"
#include "qualifiedid.h"
#include "scriptengine.h"
#include "value.h"
@@ -57,7 +58,6 @@
#include <logging/translator.h>
#include <tools/error.h>
#include <tools/fileinfo.h>
-#include <tools/jsliterals.h>
#include <tools/preferences.h>
#include <tools/profile.h>
#include <tools/profiling.h>
@@ -75,7 +75,6 @@
#include <QtCore/qdiriterator.h>
#include <QtCore/qjsondocument.h>
#include <QtCore/qjsonobject.h>
-#include <QtCore/qtemporaryfile.h>
#include <QtCore/qtextstream.h>
#include <QtCore/qthreadstorage.h>
#include <QtScript/qscriptvalueiterator.h>
@@ -254,6 +253,7 @@ ModuleLoader::ModuleLoader(Evaluator *evaluator, Logger &logger)
, m_progressObserver(nullptr)
, m_reader(std::make_unique<ItemReader>(logger))
, m_evaluator(evaluator)
+ , m_moduleProviderLoader(std::make_unique<ModuleProviderLoader>(m_reader.get(), m_evaluator))
{
}
@@ -289,7 +289,7 @@ void ModuleLoader::setStoredProfiles(const QVariantMap &profiles)
void ModuleLoader::setStoredModuleProviderInfo(const ModuleProviderInfoList &moduleProviderInfo)
{
- m_moduleProviderInfo = moduleProviderInfo;
+ m_moduleProviderLoader->setModuleProviderInfo(moduleProviderInfo);
}
ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters &parameters)
@@ -304,6 +304,7 @@ ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters &parameters)
m_disabledItems.clear();
m_reader->clearExtraSearchPathsStack();
m_reader->setEnableTiming(parameters.logElapsedTime());
+ m_moduleProviderLoader->setProjectParameters(m_parameters);
m_elapsedTimeProbes = m_elapsedTimePrepareProducts = m_elapsedTimeHandleProducts
= m_elapsedTimeProductDependencies = m_elapsedTimeTransitiveDependencies
= m_elapsedTimePropertyChecking = 0;
@@ -379,7 +380,7 @@ ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters &parameters)
handleTopLevelProject(&result, root, buildDirectory,
Set<QString>() << QDir::cleanPath(parameters.projectFilePath()));
result.root = root;
- result.qbsFiles = m_reader->filesRead() - m_tempQbsFiles;
+ result.qbsFiles = m_reader->filesRead() - m_moduleProviderLoader->tempQbsFiles();
for (auto it = m_localProfiles.cbegin(); it != m_localProfiles.cend(); ++it)
result.profileConfigs.remove(it.key());
printProfilingInfo();
@@ -641,7 +642,7 @@ void ModuleLoader::handleTopLevelProject(ModuleLoaderResult *loadResult, Item *p
}
loadResult->projectProbes = tlp.probes;
- loadResult->moduleProviderInfo = m_moduleProviderInfo;
+ loadResult->moduleProviderInfo = m_moduleProviderLoader->moduleProviderInfo();
m_reader->clearExtraSearchPathsStack();
AccumulatingTimer timer(m_parameters.logElapsedTime()
@@ -2059,14 +2060,7 @@ bool ModuleLoader::mergeExportItems(const ProductContext &productContext)
Item *ModuleLoader::loadItemFromFile(const QString &filePath,
const CodeLocation &referencingLocation)
{
- Item *item;
- try {
- item = m_reader->readFile(filePath);
- } catch (const ErrorInfo &e) {
- if (e.hasLocation())
- throw;
- throw ErrorInfo(e.toString(), referencingLocation);
- }
+ Item *item = m_reader->readFile(filePath, referencingLocation);
handleAllPropertyOptionsItems(item);
return item;
}
@@ -2241,20 +2235,7 @@ void ModuleLoader::setSearchPathsForProduct(ModuleLoader::ProductContext *produc
product->searchPaths << p;
}
- // Existing module provider search paths are re-used if and only if the provider configuration
- // at setup time was the same as the current one for the respective module provider.
- if (!m_moduleProviderInfo.empty()) {
- const QVariantMap configForProduct = moduleProviderConfig(*product);
- for (const ModuleProviderInfo &c : m_moduleProviderInfo) {
- if (configForProduct.value(c.name.toString()).toMap() == c.config) {
- qCDebug(lcModuleLoader) << "re-using search paths" << c.searchPaths
- << "from module provider" << c.name
- << "for product" << product->name;
- product->knownModuleProviders.insert(c.name);
- product->searchPaths << c.searchPaths;
- }
- }
- }
+ m_moduleProviderLoader->setupKnownModuleProviders(*product);
}
ModuleLoader::ShadowProductInfo ModuleLoader::getShadowProductInfo(
@@ -3122,28 +3103,11 @@ Item *ModuleLoader::searchAndLoadModuleFile(ProductContext *productContext,
auto existingPaths = findExistingModulePaths(m_reader->allSearchPaths(), moduleName);
if (existingPaths.isEmpty()) { // no suitable names found, try to use providers
- bool moduleAlreadyKnown = false;
- ModuleProviderResult result;
- for (QualifiedId providerName = moduleName; !providerName.empty();
- providerName.pop_back()) {
- if (!productContext->knownModuleProviders.insert(providerName).second) {
- moduleAlreadyKnown = true;
- break;
- }
- qCDebug(lcModuleLoader) << "Module" << moduleName.toString()
- << "not found, checking for module providers";
- result = findModuleProvider(providerName, *productContext,
- ModuleProviderLookup::Regular, dependsItemLocation);
- if (result.providerFound)
- break;
- }
- if (fallbackMode == FallbackMode::Enabled && !result.providerFound
- && !moduleAlreadyKnown) {
- qCDebug(lcModuleLoader) << "Specific module provider not found for"
- << moduleName.toString() << ", setting up fallback.";
- result = findModuleProvider(moduleName, *productContext,
- ModuleProviderLookup::Fallback, dependsItemLocation);
- }
+ const auto result = m_moduleProviderLoader->executeModuleProvider(
+ *productContext,
+ dependsItemLocation,
+ moduleName,
+ fallbackMode);
if (result.providerAddedSearchPaths) {
qCDebug(lcModuleLoader) << "Re-checking for module" << moduleName.toString()
<< "with newly added search paths from module provider";
@@ -3813,152 +3777,6 @@ QStringList ModuleLoader::findExistingModulePaths(
return result;
}
-QVariantMap ModuleLoader::moduleProviderConfig(ModuleLoader::ProductContext &product)
-{
- if (product.theModuleProviderConfig)
- return *product.theModuleProviderConfig;
- QVariantMap providerConfig;
- const ItemValueConstPtr configItemValue
- = product.item->itemProperty(StringConstants::moduleProviders());
- if (configItemValue) {
- const std::function<void(const Item *, QualifiedId)> collectMap
- = [this, &providerConfig, &collectMap](const Item *item, const QualifiedId &name) {
- const Item::PropertyMap &props = item->properties();
- for (auto it = props.begin(); it != props.end(); ++it) {
- QVariant value;
- switch (it.value()->type()) {
- case Value::ItemValueType: {
- const auto childItem = static_cast<ItemValue *>(it.value().get())->item();
- childItem->setScope(item->scope());
- collectMap(childItem, QualifiedId(name) << it.key());
- continue;
- }
- case Value::JSSourceValueType:
- value = m_evaluator->value(item, it.key()).toVariant();
- break;
- case Value::VariantValueType:
- value = static_cast<VariantValue *>(it.value().get())->value();
- break;
- }
- QVariantMap m = providerConfig.value(name.toString()).toMap();
- m.insert(it.key(), value);
- providerConfig.insert(name.toString(), m);
- }
- };
- configItemValue->item()->setScope(product.item);
- collectMap(configItemValue->item(), QualifiedId());
- }
- for (auto it = product.moduleProperties.begin(); it != product.moduleProperties.end(); ++it) {
- if (!it.key().startsWith(QStringLiteral("moduleProviders.")))
- continue;
- const QString provider = it.key().mid(QStringLiteral("moduleProviders.").size());
- const QVariantMap providerConfigFromBuildConfig = it.value().toMap();
- if (providerConfigFromBuildConfig.empty())
- continue;
- QVariantMap currentMapForProvider = providerConfig.value(provider).toMap();
- for (auto propIt = providerConfigFromBuildConfig.begin();
- propIt != providerConfigFromBuildConfig.end(); ++propIt) {
- currentMapForProvider.insert(propIt.key(), propIt.value());
- }
- providerConfig.insert(provider, currentMapForProvider);
- }
- return *(product.theModuleProviderConfig = std::move(providerConfig));
-}
-
-ModuleLoader::ModuleProviderResult ModuleLoader::findModuleProvider(const QualifiedId &name,
- ModuleLoader::ProductContext &product, ModuleProviderLookup lookupType,
- const CodeLocation &dependsItemLocation)
-{
- for (const QString &path : m_reader->allSearchPaths()) {
- QString fullPath = FileInfo::resolvePath(path, QStringLiteral("module-providers"));
- switch (lookupType) {
- case ModuleProviderLookup::Regular:
- for (const QString &component : name)
- fullPath = FileInfo::resolvePath(fullPath, component);
- break;
- case ModuleProviderLookup::Fallback:
- fullPath = FileInfo::resolvePath(fullPath, QStringLiteral("__fallback"));
- break;
- }
- const QString providerFile = FileInfo::resolvePath(fullPath,
- QStringLiteral("provider.qbs"));
- if (!FileInfo::exists(providerFile)) {
- qCDebug(lcModuleLoader) << "No module provider found at" << providerFile;
- continue;
- }
- QTemporaryFile dummyItemFile;
- if (!dummyItemFile.open()) {
- throw ErrorInfo(Tr::tr("Failed to create temporary file for running module provider "
- "for dependency '%1': %2").arg(name.toString(),
- dummyItemFile.errorString()));
- }
- m_tempQbsFiles << dummyItemFile.fileName();
- qCDebug(lcModuleLoader) << "Instantiating module provider at" << providerFile;
- const QString projectBuildDir = product.project->item->variantProperty(
- StringConstants::buildDirectoryProperty())->value().toString();
- const QString searchPathBaseDir = ModuleProviderInfo::outputDirPath(projectBuildDir, name);
- const QVariant moduleConfig = moduleProviderConfig(product).value(name.toString());
- QTextStream stream(&dummyItemFile);
- using Qt::endl;
- setupDefaultCodec(stream);
- stream << "import qbs.FileInfo" << endl;
- stream << "import qbs.Utilities" << endl;
- stream << "import '" << providerFile << "' as Provider" << endl;
- stream << "Provider {" << endl;
- stream << " name: " << toJSLiteral(name.toString()) << endl;
- stream << " property var config: (" << toJSLiteral(moduleConfig) << ')' << endl;
- stream << " outputBaseDir: FileInfo.joinPaths(baseDirPrefix, "
- " Utilities.getHash(JSON.stringify(config)))" << endl;
- stream << " property string baseDirPrefix: " << toJSLiteral(searchPathBaseDir) << endl;
- stream << " property stringList searchPaths: (relativeSearchPaths || [])"
- " .map(function(p) { return FileInfo.joinPaths(outputBaseDir, p); })"
- << endl;
- stream << "}" << endl;
- stream.flush();
- Item * const providerItem = loadItemFromFile(dummyItemFile.fileName(), dependsItemLocation);
- if (providerItem->type() != ItemType::ModuleProvider) {
- throw ErrorInfo(Tr::tr("File '%1' declares an item of type '%2', "
- "but '%3' was expected.")
- .arg(providerFile, providerItem->typeName(),
- BuiltinDeclarations::instance().nameForType(ItemType::ModuleProvider)));
- }
- providerItem->setParent(product.item);
- const QVariantMap configMap = moduleConfig.toMap();
- for (auto it = configMap.begin(); it != configMap.end(); ++it) {
- const PropertyDeclaration decl = providerItem->propertyDeclaration(it.key());
- if (!decl.isValid()) {
- throw ErrorInfo(Tr::tr("No such property '%1' in module provider '%2'.")
- .arg(it.key(), name.toString()));
- }
- providerItem->setProperty(it.key(), VariantValue::create(it.value()));
- }
- EvalContextSwitcher contextSwitcher(m_evaluator->engine(), EvalContext::ModuleProvider);
- const QStringList searchPaths
- = m_evaluator->stringListValue(providerItem, QStringLiteral("searchPaths"));
- const auto addToGlobalInfo = [=] {
- m_moduleProviderInfo.emplace_back(ModuleProviderInfo(name, moduleConfig.toMap(),
- searchPaths, m_parameters.dryRun()));
- };
- if (searchPaths.empty()) {
- qCDebug(lcModuleLoader) << "Module provider did run, but did not set up "
- "any modules.";
- addToGlobalInfo();
- return {true, false};
- }
- qCDebug(lcModuleLoader) << "Module provider added" << searchPaths.size()
- << "new search path(s)";
-
- // (1) is needed so the immediate new look-up works.
- // (2) is needed so the next use of SearchPathManager considers the new paths.
- // (3) is needed for possible re-use in subsequent products and builds.
- m_reader->pushExtraSearchPaths(searchPaths); // (1)
- product.searchPaths << searchPaths; // (2)
- addToGlobalInfo(); // (3)
- return {true, true};
- }
- return {};
-}
-
void ModuleLoader::setScopeForDescendants(Item *item, Item *scope)
{
for (Item * const child : item->children()) {
diff --git a/src/lib/corelib/language/moduleloader.h b/src/lib/corelib/language/moduleloader.h
index a4cadd4fa..6d0943708 100644
--- a/src/lib/corelib/language/moduleloader.h
+++ b/src/lib/corelib/language/moduleloader.h
@@ -73,6 +73,7 @@ namespace Internal {
class Evaluator;
class Item;
class ItemReader;
+class ModuleProviderLoader;
class ProgressObserver;
class QualifiedId;
@@ -137,6 +138,7 @@ public:
ModuleLoaderResult load(const SetupProjectParameters &parameters);
private:
+ friend class ModuleProviderLoader;
class ProductSortByDependencies;
class ContextBase
@@ -343,19 +345,6 @@ private:
QStringList findExistingModulePaths(
const QStringList &searchPaths, const QualifiedId &moduleName);
- enum class ModuleProviderLookup { Regular, Fallback };
- struct ModuleProviderResult
- {
- ModuleProviderResult() = default;
- ModuleProviderResult(bool ran, bool added)
- : providerFound(ran), providerAddedSearchPaths(added) {}
- bool providerFound = false;
- bool providerAddedSearchPaths = false;
- };
- ModuleProviderResult findModuleProvider(const QualifiedId &name, ProductContext &product,
- ModuleProviderLookup lookupType, const CodeLocation &dependsItemLocation);
- QVariantMap moduleProviderConfig(ProductContext &product);
-
static void setScopeForDescendants(Item *item, Item *scope);
void overrideItemProperties(Item *item, const QString &buildConfigKey,
const QVariantMap &buildConfig);
@@ -413,6 +402,7 @@ private:
ProgressObserver *m_progressObserver;
const std::unique_ptr<ItemReader> m_reader;
Evaluator *m_evaluator;
+ const std::unique_ptr<ModuleProviderLoader> m_moduleProviderLoader;
QMap<QString, QStringList> m_moduleDirListCache;
QHash<std::pair<QString, QualifiedId>, std::optional<QString>> m_existingModulePathCache;
@@ -453,9 +443,6 @@ private:
std::unordered_map<ProductContext *, Set<DeferredDependsContext>> m_productsWithDeferredDependsItems;
Set<Item *> m_exportsWithDeferredDependsItems;
- ModuleProviderInfoList m_moduleProviderInfo;
- Set<QString> m_tempQbsFiles;
-
SetupProjectParameters m_parameters;
std::unique_ptr<Settings> m_settings;
Version m_qbsVersion;
diff --git a/src/lib/corelib/language/moduleproviderloader.cpp b/src/lib/corelib/language/moduleproviderloader.cpp
new file mode 100644
index 000000000..a4adef97a
--- /dev/null
+++ b/src/lib/corelib/language/moduleproviderloader.cpp
@@ -0,0 +1,269 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com)
+** 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 "moduleproviderloader.h"
+
+#include "builtindeclarations.h"
+#include "evaluator.h"
+#include "itemreader.h"
+#include "moduleloader.h"
+
+#include <language/scriptengine.h>
+#include <language/value.h>
+
+#include <logging/categories.h>
+#include <logging/translator.h>
+
+#include <tools/fileinfo.h>
+#include <tools/jsliterals.h>
+#include <tools/stringconstants.h>
+
+#include <QtCore/qtemporaryfile.h>
+
+namespace qbs {
+namespace Internal {
+
+ModuleProviderLoader::ModuleProviderLoader(ItemReader *reader, Evaluator *evaluator)
+ : m_reader(reader)
+ , m_evaluator(evaluator)
+{
+}
+
+void ModuleProviderLoader::setupKnownModuleProviders(ProductContext &product)
+{
+ // Existing module provider search paths are re-used if and only if the provider configuration
+ // at setup time was the same as the current one for the respective module provider.
+ if (!m_moduleProviderInfo.empty()) {
+ const QVariantMap configForProduct = moduleProviderConfig(product);
+ for (const ModuleProviderInfo &c : m_moduleProviderInfo) {
+ if (configForProduct.value(c.name.toString()).toMap() == c.config) {
+ qCDebug(lcModuleLoader) << "re-using search paths" << c.searchPaths
+ << "from module provider" << c.name
+ << "for product" << product.name;
+ product.knownModuleProviders.insert(c.name);
+ product.searchPaths << c.searchPaths;
+ }
+ }
+ }
+}
+
+ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModuleProvider(
+ ProductContext &productContext,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName,
+ FallbackMode fallbackMode)
+{
+ bool moduleAlreadyKnown = false;
+ ModuleProviderResult result;
+ for (QualifiedId providerName = moduleName; !providerName.empty();
+ providerName.pop_back()) {
+ if (!productContext.knownModuleProviders.insert(providerName).second) {
+ moduleAlreadyKnown = true;
+ break;
+ }
+ qCDebug(lcModuleLoader) << "Module" << moduleName.toString()
+ << "not found, checking for module providers";
+ result = findModuleProvider(providerName, productContext,
+ ModuleProviderLookup::Regular, dependsItemLocation);
+ if (result.providerFound)
+ break;
+ }
+ if (fallbackMode == FallbackMode::Enabled && !result.providerFound
+ && !moduleAlreadyKnown) {
+ qCDebug(lcModuleLoader) << "Specific module provider not found for"
+ << moduleName.toString() << ", setting up fallback.";
+ result = findModuleProvider(moduleName, productContext,
+ ModuleProviderLookup::Fallback, dependsItemLocation);
+ }
+ return result;
+}
+
+ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::findModuleProvider(
+ const QualifiedId &name,
+ ProductContext &product,
+ ModuleProviderLookup lookupType,
+ const CodeLocation &dependsItemLocation)
+{
+ for (const QString &path : m_reader->allSearchPaths()) {
+ QString fullPath = FileInfo::resolvePath(path, QStringLiteral("module-providers"));
+ switch (lookupType) {
+ case ModuleProviderLookup::Regular:
+ for (const QString &component : name)
+ fullPath = FileInfo::resolvePath(fullPath, component);
+ break;
+ case ModuleProviderLookup::Fallback:
+ fullPath = FileInfo::resolvePath(fullPath, QStringLiteral("__fallback"));
+ break;
+ }
+ const QString providerFile = FileInfo::resolvePath(fullPath,
+ QStringLiteral("provider.qbs"));
+ if (!FileInfo::exists(providerFile)) {
+ qCDebug(lcModuleLoader) << "No module provider found at" << providerFile;
+ continue;
+ }
+ QTemporaryFile dummyItemFile;
+ if (!dummyItemFile.open()) {
+ throw ErrorInfo(Tr::tr("Failed to create temporary file for running module provider "
+ "for dependency '%1': %2").arg(name.toString(),
+ dummyItemFile.errorString()));
+ }
+ m_tempQbsFiles << dummyItemFile.fileName();
+ qCDebug(lcModuleLoader) << "Instantiating module provider at" << providerFile;
+ const QString projectBuildDir = product.project->item->variantProperty(
+ StringConstants::buildDirectoryProperty())->value().toString();
+ const QString searchPathBaseDir = ModuleProviderInfo::outputDirPath(projectBuildDir, name);
+ const QVariant moduleConfig = moduleProviderConfig(product).value(name.toString());
+ QTextStream stream(&dummyItemFile);
+ using Qt::endl;
+ setupDefaultCodec(stream);
+ stream << "import qbs.FileInfo" << endl;
+ stream << "import qbs.Utilities" << endl;
+ stream << "import '" << providerFile << "' as Provider" << endl;
+ stream << "Provider {" << endl;
+ stream << " name: " << toJSLiteral(name.toString()) << endl;
+ stream << " property var config: (" << toJSLiteral(moduleConfig) << ')' << endl;
+ stream << " outputBaseDir: FileInfo.joinPaths(baseDirPrefix, "
+ " Utilities.getHash(JSON.stringify(config)))" << endl;
+ stream << " property string baseDirPrefix: " << toJSLiteral(searchPathBaseDir) << endl;
+ stream << " property stringList searchPaths: (relativeSearchPaths || [])"
+ " .map(function(p) { return FileInfo.joinPaths(outputBaseDir, p); })"
+ << endl;
+ stream << "}" << endl;
+ stream.flush();
+ Item * const providerItem =
+ m_reader->readFile(dummyItemFile.fileName(), dependsItemLocation);
+ if (providerItem->type() != ItemType::ModuleProvider) {
+ throw ErrorInfo(Tr::tr("File '%1' declares an item of type '%2', "
+ "but '%3' was expected.")
+ .arg(providerFile, providerItem->typeName(),
+ BuiltinDeclarations::instance().nameForType(ItemType::ModuleProvider)));
+ }
+ providerItem->setParent(product.item);
+ const QVariantMap configMap = moduleConfig.toMap();
+ for (auto it = configMap.begin(); it != configMap.end(); ++it) {
+ const PropertyDeclaration decl = providerItem->propertyDeclaration(it.key());
+ if (!decl.isValid()) {
+ throw ErrorInfo(Tr::tr("No such property '%1' in module provider '%2'.")
+ .arg(it.key(), name.toString()));
+ }
+ providerItem->setProperty(it.key(), VariantValue::create(it.value()));
+ }
+ EvalContextSwitcher contextSwitcher(m_evaluator->engine(), EvalContext::ModuleProvider);
+ const QStringList searchPaths
+ = m_evaluator->stringListValue(providerItem, QStringLiteral("searchPaths"));
+ const auto addToGlobalInfo = [=] {
+ m_moduleProviderInfo.emplace_back(ModuleProviderInfo(name, moduleConfig.toMap(),
+ searchPaths, m_parameters.dryRun()));
+ };
+ if (searchPaths.empty()) {
+ qCDebug(lcModuleLoader) << "Module provider did run, but did not set up "
+ "any modules.";
+ addToGlobalInfo();
+ return {true, false};
+ }
+ qCDebug(lcModuleLoader) << "Module provider added" << searchPaths.size()
+ << "new search path(s)";
+
+ // (1) is needed so the immediate new look-up works.
+ // (2) is needed so the next use of SearchPathManager considers the new paths.
+ // (3) is needed for possible re-use in subsequent products and builds.
+ m_reader->pushExtraSearchPaths(searchPaths); // (1)
+ product.searchPaths << searchPaths; // (2)
+ addToGlobalInfo(); // (3)
+ return {true, true};
+ }
+ return {};
+}
+
+QVariantMap ModuleProviderLoader::moduleProviderConfig(
+ ProductContext &product)
+{
+ if (product.theModuleProviderConfig)
+ return *product.theModuleProviderConfig;
+ QVariantMap providerConfig;
+ const ItemValueConstPtr configItemValue =
+ product.item->itemProperty(StringConstants::moduleProviders());
+ if (configItemValue) {
+ const std::function<void(const Item *, QualifiedId)> collectMap
+ = [this, &providerConfig, &collectMap](const Item *item, const QualifiedId &name) {
+ const Item::PropertyMap &props = item->properties();
+ for (auto it = props.begin(); it != props.end(); ++it) {
+ QVariant value;
+ switch (it.value()->type()) {
+ case Value::ItemValueType: {
+ const auto childItem = static_cast<ItemValue *>(it.value().get())->item();
+ childItem->setScope(item->scope());
+ collectMap(childItem, QualifiedId(name) << it.key());
+ continue;
+ }
+ case Value::JSSourceValueType:
+ value = m_evaluator->value(item, it.key()).toVariant();
+ break;
+ case Value::VariantValueType:
+ value = static_cast<VariantValue *>(it.value().get())->value();
+ break;
+ }
+ QVariantMap m = providerConfig.value(name.toString()).toMap();
+ m.insert(it.key(), value);
+ providerConfig.insert(name.toString(), m);
+ }
+ };
+ configItemValue->item()->setScope(product.item);
+ collectMap(configItemValue->item(), QualifiedId());
+ }
+ for (auto it = product.moduleProperties.begin(); it != product.moduleProperties.end(); ++it) {
+ if (!it.key().startsWith(QStringLiteral("moduleProviders.")))
+ continue;
+ const QString provider = it.key().mid(QStringLiteral("moduleProviders.").size());
+ const QVariantMap providerConfigFromBuildConfig = it.value().toMap();
+ if (providerConfigFromBuildConfig.empty())
+ continue;
+ QVariantMap currentMapForProvider = providerConfig.value(provider).toMap();
+ for (auto propIt = providerConfigFromBuildConfig.begin();
+ propIt != providerConfigFromBuildConfig.end(); ++propIt) {
+ currentMapForProvider.insert(propIt.key(), propIt.value());
+ }
+ providerConfig.insert(provider, currentMapForProvider);
+ }
+ return *(product.theModuleProviderConfig = std::move(providerConfig));
+}
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/lib/corelib/language/moduleproviderloader.h b/src/lib/corelib/language/moduleproviderloader.h
new file mode 100644
index 000000000..5184bdeab
--- /dev/null
+++ b/src/lib/corelib/language/moduleproviderloader.h
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com)
+** 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$
+**
+****************************************************************************/
+
+#ifndef MODULEPROVIDERLOADER_H
+#define MODULEPROVIDERLOADER_H
+
+#include "moduleloader.h"
+#include "moduleproviderinfo.h"
+
+#include <QtCore/qmap.h>
+#include <QtCore/qvariant.h>
+
+namespace qbs {
+namespace Internal {
+
+class ModuleProviderLoader
+{
+public:
+ using ProductContext = ModuleLoader::ProductContext;
+ using FallbackMode = ModuleLoader::FallbackMode;
+ explicit ModuleProviderLoader(ItemReader *itemReader, Evaluator *evaluator);
+
+ enum class ModuleProviderLookup { Regular, Fallback };
+ struct ModuleProviderResult
+ {
+ ModuleProviderResult() = default;
+ ModuleProviderResult(bool ran, bool added)
+ : providerFound(ran), providerAddedSearchPaths(added) {}
+ bool providerFound = false;
+ bool providerAddedSearchPaths = false;
+ };
+
+ const ModuleProviderInfoList &moduleProviderInfo() const { return m_moduleProviderInfo; }
+ void setModuleProviderInfo(ModuleProviderInfoList moduleProviderInfo)
+ {
+ m_moduleProviderInfo = std::move(moduleProviderInfo);
+ }
+
+ void setProjectParameters(SetupProjectParameters parameters)
+ {
+ m_parameters = std::move(parameters);
+ }
+
+ void setupKnownModuleProviders(ProductContext &product);
+ ModuleProviderResult executeModuleProvider(
+ ProductContext &productContext,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName,
+ FallbackMode fallbackMode);
+ ModuleProviderResult findModuleProvider(
+ const QualifiedId &name,
+ ProductContext &product,
+ ModuleProviderLookup lookupType,
+ const CodeLocation &dependsItemLocation);
+ QVariantMap moduleProviderConfig(ProductContext &product);
+
+ const Set<QString> &tempQbsFiles() const { return m_tempQbsFiles; }
+
+private:
+ ItemReader *const m_reader{nullptr};
+ Evaluator *const m_evaluator{nullptr};
+
+ SetupProjectParameters m_parameters;
+ ModuleProviderInfoList m_moduleProviderInfo;
+ Set<QString> m_tempQbsFiles;
+};
+
+} // namespace Internal
+} // namespace qbs
+
+#endif // MODULEPROVIDERLOADER_H