/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #include namespace qbs { namespace Internal { ModuleProviderLoader::ModuleProviderLoader( const SetupProjectParameters ¶meters, 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 providersToRun; qCDebug(lcModuleLoader) << "Module" << moduleName.toString() << "not found, checking for module providers"; const auto providerNames = getModuleProviders(productContext.productItem); if (providerNames) { providersToRun = transformed>(*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 &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 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(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(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> ModuleProviderLoader::getModuleProviders(Item *item) { while (item) { const auto providers = m_evaluator.optionalStringListValue(item, StringConstants::qbsModuleProviders()); if (providers) { return transformed>(*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( 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( 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 > 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 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