diff options
author | Ivan Komissarov <abbapoh@gmail.com> | 2021-05-16 17:52:17 +0200 |
---|---|---|
committer | Ivan Komissarov <ABBAPOH@gmail.com> | 2021-05-19 14:10:37 +0000 |
commit | 0bbefbd5dc0ed597b8d1442594a9ab04fadaba60 (patch) | |
tree | af3ca87a51f25fa7f8d1d34b165efda905aaf922 /src/lib/corelib/language | |
parent | c7a1ec06313f85a9cf45b44f5940a952348d66d2 (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.cpp | 11 | ||||
-rw-r--r-- | src/lib/corelib/language/itemreader.h | 1 | ||||
-rw-r--r-- | src/lib/corelib/language/language.pri | 2 | ||||
-rw-r--r-- | src/lib/corelib/language/moduleloader.cpp | 208 | ||||
-rw-r--r-- | src/lib/corelib/language/moduleloader.h | 19 | ||||
-rw-r--r-- | src/lib/corelib/language/moduleproviderloader.cpp | 269 | ||||
-rw-r--r-- | src/lib/corelib/language/moduleproviderloader.h | 108 |
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 ¶meters) @@ -304,6 +304,7 @@ ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters ¶meters) 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 ¶meters) 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 ¶meters); 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 |