aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/corelib/loader/moduleproviderloader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/corelib/loader/moduleproviderloader.cpp')
-rw-r--r--src/lib/corelib/loader/moduleproviderloader.cpp372
1 files changed, 372 insertions, 0 deletions
diff --git a/src/lib/corelib/loader/moduleproviderloader.cpp b/src/lib/corelib/loader/moduleproviderloader.cpp
new file mode 100644
index 000000000..584f43166
--- /dev/null
+++ b/src/lib/corelib/loader/moduleproviderloader.cpp
@@ -0,0 +1,372 @@
+/****************************************************************************
+**
+** 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 "itemreader.h"
+#include "probesresolver.h"
+
+#include <language/builtindeclarations.h>
+#include <language/evaluator.h>
+#include <language/item.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/scripttools.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stlutils.h>
+#include <tools/stringconstants.h>
+
+#include <QtCore/qtemporaryfile.h>
+
+namespace qbs {
+namespace Internal {
+
+ModuleProviderLoader::ModuleProviderLoader(
+ const SetupProjectParameters &parameters, ItemReader &reader, Evaluator &evaluator,
+ ProbesResolver &probesResolver, Logger &logger)
+ : m_parameters(parameters)
+ , m_reader(reader)
+ , m_evaluator(evaluator)
+ , m_probesResolver(probesResolver)
+ , m_logger(logger)
+{
+}
+
+ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModuleProviders(
+ const ProductContext &productContext,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName,
+ FallbackMode fallbackMode)
+{
+ ModuleProviderLoader::ModuleProviderResult result;
+ std::vector<Provider> providersToRun;
+ qCDebug(lcModuleLoader) << "Module" << moduleName.toString()
+ << "not found, checking for module providers";
+ const auto providerNames = getModuleProviders(productContext.productItem);
+ if (providerNames) {
+ providersToRun = transformed<std::vector<Provider>>(*providerNames, [](const auto &name) {
+ return Provider{name, ModuleProviderLookup::Named}; });
+ } else {
+ for (QualifiedId providerName = moduleName; !providerName.empty();
+ providerName.pop_back()) {
+ providersToRun.push_back({providerName, ModuleProviderLookup::Scoped});
+ }
+ }
+ result = executeModuleProvidersHelper(productContext, dependsItemLocation, providersToRun);
+
+ if (fallbackMode == FallbackMode::Enabled
+ && !result.providerFound
+ && !providerNames) {
+ qCDebug(lcModuleLoader) << "Specific module provider not found for"
+ << moduleName.toString() << ", setting up fallback.";
+ result = executeModuleProvidersHelper(
+ productContext,
+ dependsItemLocation,
+ {{moduleName, ModuleProviderLookup::Fallback}});
+ }
+
+ return result;
+}
+
+ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModuleProvidersHelper(
+ const ProductContext &product,
+ const CodeLocation &dependsItemLocation,
+ const std::vector<Provider> &providers)
+{
+ if (providers.empty())
+ return {};
+ QStringList allSearchPaths;
+ ModuleProviderResult result;
+ result.providerConfig = product.providerConfig ? *product.providerConfig
+ : getModuleProviderConfig(product);
+ const auto qbsModule = evaluateQbsModule(product);
+ for (const auto &[name, lookupType] : providers) {
+ const QVariantMap config = result.providerConfig.value(name.toString()).toMap();
+ ModuleProviderInfo &info = m_storedModuleProviderInfo.providers[
+ {name.toString(), config, qbsModule, int(lookupType)}];
+ const bool fromCache = !info.name.isEmpty();
+ if (!fromCache) {
+ info.name = name;
+ info.config = config;
+ info.providerFile = findModuleProviderFile(name, lookupType);
+ if (!info.providerFile.isEmpty()) {
+ qCDebug(lcModuleLoader) << "Running provider" << name << "at" << info.providerFile;
+ const auto evalResult = evaluateModuleProvider(
+ product, dependsItemLocation, name, info.providerFile, config, qbsModule);
+ info.searchPaths = evalResult.first;
+ result.probes << evalResult.second;
+ info.transientOutput = m_parameters.dryRun();
+ }
+ }
+ if (info.providerFile.isEmpty()) {
+ if (lookupType == ModuleProviderLookup::Named)
+ throw ErrorInfo(Tr::tr("Unknown provider '%1'").arg(name.toString()));
+ continue;
+ }
+ if (fromCache)
+ qCDebug(lcModuleLoader) << "Re-using provider" << name.toString() << "from cache";
+
+ result.providerFound = true;
+ if (info.searchPaths.empty()) {
+ qCDebug(lcModuleLoader)
+ << "Module provider did run, but did not set up any modules.";
+ continue;
+ }
+ qCDebug(lcModuleLoader) << "Module provider added" << info.searchPaths.size()
+ << "new search path(s)";
+
+ allSearchPaths << info.searchPaths;
+ }
+ if (allSearchPaths.isEmpty())
+ return result;
+
+ result.searchPaths = std::move(allSearchPaths);
+
+ return result;
+}
+
+QVariantMap ModuleProviderLoader::getModuleProviderConfig(const ProductContext &product)
+{
+ QVariantMap providerConfig;
+ const ItemValueConstPtr configItemValue =
+ product.productItem->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: {
+ const ScopedJsValue sv(m_evaluator.engine()->context(),
+ m_evaluator.value(item, it.key()));
+ value = getJsVariant(m_evaluator.engine()->context(), sv);
+ 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.productItem);
+ 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 providerConfig;
+}
+
+std::optional<std::vector<QualifiedId>> ModuleProviderLoader::getModuleProviders(Item *item)
+{
+ while (item) {
+ const auto providers =
+ m_evaluator.optionalStringListValue(item, StringConstants::qbsModuleProviders());
+ if (providers) {
+ return transformed<std::vector<QualifiedId>>(*providers, [](const auto &provider) {
+ return QualifiedId::fromString(provider); });
+ }
+ item = item->parent();
+ }
+ return std::nullopt;
+}
+
+QString ModuleProviderLoader::findModuleProviderFile(
+ const QualifiedId &name, ModuleProviderLookup lookupType)
+{
+ for (const QString &path : m_reader.allSearchPaths()) {
+ QString fullPath = FileInfo::resolvePath(path, QStringLiteral("module-providers"));
+ switch (lookupType) {
+ case ModuleProviderLookup::Named: {
+ const auto result =
+ FileInfo::resolvePath(fullPath, name.toString() + QStringLiteral(".qbs"));
+ if (FileInfo::exists(result)) {
+ fullPath = result;
+ break;
+ }
+ [[fallthrough]];
+ }
+ case ModuleProviderLookup::Scoped:
+ for (const QString &component : name)
+ fullPath = FileInfo::resolvePath(fullPath, component);
+ fullPath = FileInfo::resolvePath(fullPath, QStringLiteral("provider.qbs"));
+ break;
+ case ModuleProviderLookup::Fallback:
+ fullPath = FileInfo::resolvePath(fullPath, QStringLiteral("__fallback/provider.qbs"));
+ break;
+ }
+ if (!FileInfo::exists(fullPath)) {
+ qCDebug(lcModuleLoader) << "No module provider found at" << fullPath;
+ continue;
+ }
+ return fullPath;
+ }
+ return {};
+}
+
+QVariantMap ModuleProviderLoader::evaluateQbsModule(const ProductContext &product) const
+{
+ const QString properties[] = {
+ QStringLiteral("sysroot"),
+ QStringLiteral("toolchain"),
+ };
+ const auto qbsItemValue = std::static_pointer_cast<ItemValue>(
+ product.productItem->property(StringConstants::qbsModule()));
+ QVariantMap result;
+ for (const auto &property : properties) {
+ const ScopedJsValue val(m_evaluator.engine()->context(),
+ m_evaluator.value(qbsItemValue->item(), property));
+ auto value = getJsVariant(m_evaluator.engine()->context(), val);
+ if (!value.isValid())
+ continue;
+
+ // The xcode module sets qbs.sysroot; the resulting value is bogus before the probes
+ // have run.
+ if (property == QLatin1String("sysroot") && !FileInfo::isAbsolute(value.toString()))
+ continue;
+
+ result[property] = std::move(value);
+ }
+ return result;
+}
+
+Item *ModuleProviderLoader::createProviderScope(const ProductContext &product, const QVariantMap &qbsModule)
+{
+ const auto qbsItemValue = std::static_pointer_cast<ItemValue>(
+ product.productItem->property(StringConstants::qbsModule()));
+
+ Item *fakeQbsModule = Item::create(product.productItem->pool(), ItemType::Scope);
+
+ for (auto it = qbsModule.begin(), end = qbsModule.end(); it != end; ++it) {
+ fakeQbsModule->setProperty(it.key(), VariantValue::create(it.value()));
+ }
+
+ Item *scope = Item::create(product.productItem->pool(), ItemType::Scope);
+ scope->setFile(qbsItemValue->item()->file());
+ scope->setProperty(StringConstants::qbsModule(), ItemValue::create(fakeQbsModule));
+ return scope;
+}
+
+std::pair<QStringList, std::vector<ProbeConstPtr> > ModuleProviderLoader::evaluateModuleProvider(const ProductContext &product,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &name,
+ const QString &providerFile,
+ const QVariantMap &moduleConfig,
+ const QVariantMap &qbsModule)
+{
+ 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.projectItem->variantProperty(
+ StringConstants::buildDirectoryProperty())->value().toString();
+ const QString searchPathBaseDir = ModuleProviderInfo::outputDirPath(projectBuildDir, name);
+
+ // include qbs module into hash
+ auto jsConfig = moduleConfig;
+ jsConfig[StringConstants::qbsModule()] = qbsModule;
+
+ 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(jsConfig) << ')' << 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.setupItemFromFile(
+ dummyItemFile.fileName(), dependsItemLocation, m_evaluator);
+ 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->setScope(createProviderScope(product, qbsModule));
+ providerItem->overrideProperties(moduleConfig, name, m_parameters, m_logger);
+ std::vector<ProbeConstPtr> probes = m_probesResolver.resolveProbes(
+ {product.name, product.uniqueName}, providerItem);
+
+ EvalContextSwitcher contextSwitcher(m_evaluator.engine(), EvalContext::ModuleProvider);
+ return std::make_pair(m_evaluator.stringListValue(providerItem, QStringLiteral("searchPaths")),
+ std::move(probes));
+}
+
+} // namespace Internal
+} // namespace qbs