aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/lib/corelib/CMakeLists.txt2
-rw-r--r--src/lib/corelib/corelib.qbs2
-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
9 files changed, 411 insertions, 211 deletions
diff --git a/src/lib/corelib/CMakeLists.txt b/src/lib/corelib/CMakeLists.txt
index 423f04ce7..52729143f 100644
--- a/src/lib/corelib/CMakeLists.txt
+++ b/src/lib/corelib/CMakeLists.txt
@@ -228,6 +228,8 @@ set(LANGUAGE_SOURCES
modulemerger.cpp
modulemerger.h
moduleproviderinfo.h
+ moduleproviderloader.cpp
+ moduleproviderloader.h
preparescriptobserver.cpp
preparescriptobserver.h
projectresolver.cpp
diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs
index 40a4bc44f..65644ea32 100644
--- a/src/lib/corelib/corelib.qbs
+++ b/src/lib/corelib/corelib.qbs
@@ -321,6 +321,8 @@ QbsLibrary {
"modulemerger.cpp",
"modulemerger.h",
"moduleproviderinfo.h",
+ "moduleproviderloader.cpp",
+ "moduleproviderloader.h",
"preparescriptobserver.cpp",
"preparescriptobserver.h",
"projectresolver.cpp",
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