diff options
Diffstat (limited to 'src')
27 files changed, 460 insertions, 24 deletions
diff --git a/src/app/qbs/commandlinefrontend.cpp b/src/app/qbs/commandlinefrontend.cpp index 978fdbb7a..c848b24d0 100644 --- a/src/app/qbs/commandlinefrontend.cpp +++ b/src/app/qbs/commandlinefrontend.cpp @@ -146,6 +146,7 @@ void CommandLineFrontend::start() params.setDryRun(m_parser.dryRun()); params.setForceProbeExecution(m_parser.forceProbesExecution()); params.setWaitLockBuildGraph(m_parser.waitLockBuildGraph()); + params.setFallbackProviderEnabled(!m_parser.disableFallbackProvider()); params.setLogElapsedTime(m_parser.logTime()); params.setSettingsDirectory(m_settings->baseDirectory()); params.setOverrideBuildGraphData(m_parser.command() == ResolveCommandType); diff --git a/src/app/qbs/parser/commandlineoption.cpp b/src/app/qbs/parser/commandlineoption.cpp index e18658751..dc5b4e440 100644 --- a/src/app/qbs/parser/commandlineoption.cpp +++ b/src/app/qbs/parser/commandlineoption.cpp @@ -681,6 +681,17 @@ QString WaitLockOption::longRepresentation() const return QLatin1String("--wait-lock"); } +QString DisableFallbackProviderOption::description(CommandType) const +{ + return Tr::tr("%1\n\tDo not fall back to pkg-config if a dependency is not found.\n") + .arg(longRepresentation()); +} + +QString DisableFallbackProviderOption::longRepresentation() const +{ + return QLatin1String("--no-fallback-module-provider"); +} + QString RunEnvConfigOption::description(CommandType command) const { Q_UNUSED(command); diff --git a/src/app/qbs/parser/commandlineoption.h b/src/app/qbs/parser/commandlineoption.h index d57ec76b7..414f90489 100644 --- a/src/app/qbs/parser/commandlineoption.h +++ b/src/app/qbs/parser/commandlineoption.h @@ -75,6 +75,7 @@ public: GeneratorOptionType, WaitLockOptionType, RunEnvConfigOptionType, + DisableFallbackProviderType, }; virtual ~CommandLineOption(); @@ -414,6 +415,14 @@ public: QString longRepresentation() const override; }; +class DisableFallbackProviderOption : public OnOffOption +{ +public: + QString description(CommandType command) const override; + QString shortRepresentation() const override { return QString(); } + QString longRepresentation() const override; +}; + } // namespace qbs #endif // QBS_COMMANDLINEOPTION_H diff --git a/src/app/qbs/parser/commandlineoptionpool.cpp b/src/app/qbs/parser/commandlineoptionpool.cpp index 9964f051a..63711f623 100644 --- a/src/app/qbs/parser/commandlineoptionpool.cpp +++ b/src/app/qbs/parser/commandlineoptionpool.cpp @@ -128,6 +128,9 @@ CommandLineOption *CommandLineOptionPool::getOption(CommandLineOption::Type type case CommandLineOption::WaitLockOptionType: option = new WaitLockOption; break; + case CommandLineOption::DisableFallbackProviderType: + option = new DisableFallbackProviderOption; + break; case CommandLineOption::RunEnvConfigOptionType: option = new RunEnvConfigOption; break; @@ -273,6 +276,12 @@ WaitLockOption *CommandLineOptionPool::waitLockOption() const return static_cast<WaitLockOption *>(getOption(CommandLineOption::WaitLockOptionType)); } +DisableFallbackProviderOption *CommandLineOptionPool::disableFallbackProviderOption() const +{ + return static_cast<DisableFallbackProviderOption *>( + getOption(CommandLineOption::DisableFallbackProviderType)); +} + RunEnvConfigOption *CommandLineOptionPool::runEnvConfigOption() const { return static_cast<RunEnvConfigOption *>(getOption(CommandLineOption::RunEnvConfigOptionType)); diff --git a/src/app/qbs/parser/commandlineoptionpool.h b/src/app/qbs/parser/commandlineoptionpool.h index 6a4669165..c7ac263e1 100644 --- a/src/app/qbs/parser/commandlineoptionpool.h +++ b/src/app/qbs/parser/commandlineoptionpool.h @@ -77,6 +77,7 @@ public: RespectProjectJobLimitsOption *respectProjectJobLimitsOption() const; GeneratorOption *generatorOption() const; WaitLockOption *waitLockOption() const; + DisableFallbackProviderOption *disableFallbackProviderOption() const; RunEnvConfigOption *runEnvConfigOption() const; private: diff --git a/src/app/qbs/parser/commandlineparser.cpp b/src/app/qbs/parser/commandlineparser.cpp index c2e265336..2ec0df1df 100644 --- a/src/app/qbs/parser/commandlineparser.cpp +++ b/src/app/qbs/parser/commandlineparser.cpp @@ -231,6 +231,11 @@ bool CommandLineParser::waitLockBuildGraph() const return d->optionPool.waitLockOption()->enabled(); } +bool CommandLineParser::disableFallbackProvider() const +{ + return d->optionPool.disableFallbackProviderOption()->enabled(); +} + bool CommandLineParser::logTime() const { return d->logTime; diff --git a/src/app/qbs/parser/commandlineparser.h b/src/app/qbs/parser/commandlineparser.h index e2ef8ad77..d47657b16 100644 --- a/src/app/qbs/parser/commandlineparser.h +++ b/src/app/qbs/parser/commandlineparser.h @@ -76,6 +76,7 @@ public: bool dryRun() const; bool forceProbesExecution() const; bool waitLockBuildGraph() const; + bool disableFallbackProvider() const; bool logTime() const; bool withNonDefaultProducts() const; bool buildBeforeInstalling() const; diff --git a/src/app/qbs/parser/parsercommand.cpp b/src/app/qbs/parser/parsercommand.cpp index 33f93ce53..79636ff0a 100644 --- a/src/app/qbs/parser/parsercommand.cpp +++ b/src/app/qbs/parser/parsercommand.cpp @@ -210,7 +210,8 @@ static QList<CommandLineOption::Type> resolveOptions() << CommandLineOption::ShowProgressOptionType << CommandLineOption::DryRunOptionType << CommandLineOption::ForceProbesOptionType - << CommandLineOption::LogTimeOptionType; + << CommandLineOption::LogTimeOptionType + << CommandLineOption::DisableFallbackProviderType; } QList<CommandLineOption::Type> ResolveCommand::supportedOptions() const diff --git a/src/lib/corelib/buildgraph/buildgraphloader.cpp b/src/lib/corelib/buildgraph/buildgraphloader.cpp index a1ca7afdb..e6d1cb75a 100644 --- a/src/lib/corelib/buildgraph/buildgraphloader.cpp +++ b/src/lib/corelib/buildgraph/buildgraphloader.cpp @@ -340,6 +340,8 @@ void BuildGraphLoader::trackProjectChanges() ldr.setSearchPaths(m_parameters.searchPaths()); ldr.setProgressObserver(m_evalContext->observer()); ldr.setOldProjectProbes(restoredProject->probes); + if (!m_parameters.forceProbeExecution()) + ldr.setStoredModuleProviderInfo(restoredProject->moduleProviderInfo); ldr.setLastResolveTime(restoredProject->lastResolveTime); QHash<QString, std::vector<ProbeConstPtr>> restoredProbes; for (const auto &restoredProduct : qAsConst(allRestoredProducts)) diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs index c947cb484..db00a7005 100644 --- a/src/lib/corelib/corelib.qbs +++ b/src/lib/corelib/corelib.qbs @@ -299,6 +299,7 @@ QbsLibrary { "moduleloader.h", "modulemerger.cpp", "modulemerger.h", + "moduleproviderinfo.h", "preparescriptobserver.cpp", "preparescriptobserver.h", "projectresolver.cpp", diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp index bfdfab51e..3ca0608d4 100644 --- a/src/lib/corelib/language/builtindeclarations.cpp +++ b/src/lib/corelib/language/builtindeclarations.cpp @@ -69,6 +69,7 @@ BuiltinDeclarations::BuiltinDeclarations() { QLatin1String("Group"), ItemType::Group }, { QLatin1String("JobLimit"), ItemType::JobLimit }, { QLatin1String("Module"), ItemType::Module }, + { QLatin1String("ModuleProvider"), ItemType::ModuleProvider }, { QLatin1String("Parameter"), ItemType::Parameter }, { QLatin1String("Parameters"), ItemType::Parameters }, { QLatin1String("Probe"), ItemType::Probe }, @@ -90,6 +91,7 @@ BuiltinDeclarations::BuiltinDeclarations() addGroupItem(); addJobLimitItem(); addModuleItem(); + addModuleProviderItem(); addProbeItem(); addProductItem(); addProfileItem(); @@ -244,6 +246,8 @@ void BuiltinDeclarations::addDependsItem() item << PropertyDeclaration(StringConstants::multiplexConfigurationIdsProperty(), PropertyDeclaration::StringList, QString(), PropertyDeclaration::ReadOnlyFlag); + item << PropertyDeclaration(StringConstants::enableFallbackProperty(), + PropertyDeclaration::Boolean, StringConstants::trueValue()); insert(item); } @@ -316,6 +320,16 @@ void BuiltinDeclarations::addModuleItem() insert(item); } +void BuiltinDeclarations::addModuleProviderItem() +{ + ItemDeclaration item(ItemType::ModuleProvider); + item << nameProperty() + << PropertyDeclaration(QStringLiteral("outputBaseDir"), PropertyDeclaration::String) + << PropertyDeclaration(QStringLiteral("relativeSearchPaths"), + PropertyDeclaration::StringList); + insert(item); +} + ItemDeclaration BuiltinDeclarations::moduleLikeItem(ItemType type) { ItemDeclaration item(type); diff --git a/src/lib/corelib/language/builtindeclarations.h b/src/lib/corelib/language/builtindeclarations.h index ff16b395a..988f9ab81 100644 --- a/src/lib/corelib/language/builtindeclarations.h +++ b/src/lib/corelib/language/builtindeclarations.h @@ -78,6 +78,7 @@ private: void addGroupItem(); void addJobLimitItem(); void addModuleItem(); + void addModuleProviderItem(); static ItemDeclaration moduleLikeItem(ItemType type); void addProbeItem(); void addProductItem(); diff --git a/src/lib/corelib/language/evaluatorscriptclass.cpp b/src/lib/corelib/language/evaluatorscriptclass.cpp index a5e23a131..829cb7494 100644 --- a/src/lib/corelib/language/evaluatorscriptclass.cpp +++ b/src/lib/corelib/language/evaluatorscriptclass.cpp @@ -621,7 +621,8 @@ public: || itemOfProperty->type() == ItemType::Export)) { const VariantValueConstPtr varValue = itemOfProperty->variantProperty(StringConstants::nameProperty()); - QBS_ASSERT(varValue, return); + if (!varValue) + return; m_stackUpdate = true; const QualifiedId fullPropName = QualifiedId::fromString(varValue->value().toString()) << name.toString(); diff --git a/src/lib/corelib/language/itemtype.h b/src/lib/corelib/language/itemtype.h index 324a1fb87..724666cb4 100644 --- a/src/lib/corelib/language/itemtype.h +++ b/src/lib/corelib/language/itemtype.h @@ -55,6 +55,7 @@ enum class ItemType { Group, JobLimit, Module, + ModuleProvider, Parameter, Parameters, Probe, diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp index d8a5d9162..2a9eb5b0e 100644 --- a/src/lib/corelib/language/language.cpp +++ b/src/lib/corelib/language/language.cpp @@ -594,6 +594,7 @@ TopLevelProject::TopLevelProject() TopLevelProject::~TopLevelProject() { + cleanupModuleProviderOutput(); delete bgLocker; } @@ -636,6 +637,8 @@ void TopLevelProject::store(Logger logger) qCDebug(lcBuildGraph) << "build graph is unchanged in project" << id(); return; } + for (ModuleProviderInfo &m : moduleProviderInfo) + m.transientOutput = false; const QString fileName = buildGraphFilePath(); qCDebug(lcBuildGraph) << "storing:" << fileName; PersistentPool pool(logger); @@ -661,6 +664,23 @@ void TopLevelProject::store(PersistentPool &pool) serializationOp<PersistentPool::Store>(pool); } +void TopLevelProject::cleanupModuleProviderOutput() +{ + QString error; + for (const ModuleProviderInfo &m : moduleProviderInfo) { + if (m.transientOutput) { + if (!removeDirectoryWithContents(m.outputDirPath(buildDirectory), &error)) + qCWarning(lcBuildGraph) << "Error removing module provider output:" << error; + } + } + QDir moduleProviderBaseDir(buildDirectory + QLatin1Char('/') + + ModuleProviderInfo::outputBaseDirName()); + if (moduleProviderBaseDir.exists() && moduleProviderBaseDir.isEmpty() + && !removeDirectoryWithContents(moduleProviderBaseDir.path(), &error)) { + qCWarning(lcBuildGraph) << "Error removing module provider output:" << error; + } +} + /*! * \class SourceWildCards * \brief Objects of the \c SourceWildCards class result from giving wildcards in a diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h index f9d69efff..994ad6c55 100644 --- a/src/lib/corelib/language/language.h +++ b/src/lib/corelib/language/language.h @@ -43,6 +43,7 @@ #include "filetags.h" #include "forward_decls.h" #include "jsimports.h" +#include "moduleproviderinfo.h" #include "propertydeclaration.h" #include "resolvedfilecontext.h" @@ -691,6 +692,7 @@ public: QString buildDirectory; // Not saved QProcessEnvironment environment; std::vector<ProbeConstPtr> probes; + ModuleProviderInfoList moduleProviderInfo; QHash<QString, QString> canonicalFilePathResults; // Results of calls to "File.canonicalFilePath()." QHash<QString, bool> fileExistsResults; // Results of calls to "File.exists()". @@ -722,11 +724,14 @@ private: pool.serializationOp<opType>(m_id, canonicalFilePathResults, fileExistsResults, directoryEntriesResults, fileLastModifiedResults, environment, probes, profileConfigs, overriddenValues, buildSystemFiles, - lastResolveTime, warningsEncountered, buildData); + lastResolveTime, warningsEncountered, buildData, + moduleProviderInfo); } void load(PersistentPool &pool) override; void store(PersistentPool &pool) override; + void cleanupModuleProviderOutput(); + QString m_id; QVariantMap m_buildConfiguration; }; diff --git a/src/lib/corelib/language/language.pri b/src/lib/corelib/language/language.pri index 6d68f9643..e07a671b9 100644 --- a/src/lib/corelib/language/language.pri +++ b/src/lib/corelib/language/language.pri @@ -28,6 +28,7 @@ HEADERS += \ $$PWD/loader.h \ $$PWD/moduleloader.h \ $$PWD/modulemerger.h \ + $$PWD/moduleproviderinfo.h \ $$PWD/preparescriptobserver.h \ $$PWD/projectresolver.h \ $$PWD/property.h \ diff --git a/src/lib/corelib/language/loader.cpp b/src/lib/corelib/language/loader.cpp index 98a90e221..4d2eef983 100644 --- a/src/lib/corelib/language/loader.cpp +++ b/src/lib/corelib/language/loader.cpp @@ -104,6 +104,11 @@ void Loader::setStoredProfiles(const QVariantMap &profiles) m_storedProfiles = profiles; } +void Loader::setStoredModuleProviderInfo(const ModuleProviderInfoList &providerInfo) +{ + m_storedModuleProviderInfo = providerInfo; +} + TopLevelProjectPtr Loader::loadProject(const SetupProjectParameters &_parameters) { SetupProjectParameters parameters = _parameters; @@ -160,6 +165,7 @@ TopLevelProjectPtr Loader::loadProject(const SetupProjectParameters &_parameters moduleLoader.setOldProductProbes(m_oldProductProbes); moduleLoader.setLastResolveTime(m_lastResolveTime); moduleLoader.setStoredProfiles(m_storedProfiles); + moduleLoader.setStoredModuleProviderInfo(m_storedModuleProviderInfo); const ModuleLoaderResult loadResult = moduleLoader.load(parameters); ProjectResolver resolver(&evaluator, loadResult, parameters, m_logger); resolver.setProgressObserver(m_progressObserver); diff --git a/src/lib/corelib/language/loader.h b/src/lib/corelib/language/loader.h index 9883d5b66..48a0b6065 100644 --- a/src/lib/corelib/language/loader.h +++ b/src/lib/corelib/language/loader.h @@ -40,6 +40,7 @@ #define QBS_LOADER_H #include "forward_decls.h" +#include "moduleproviderinfo.h" #include <logging/logger.h> #include <tools/filetime.h> @@ -64,6 +65,7 @@ public: void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes); void setLastResolveTime(const FileTime &time) { m_lastResolveTime = time; } void setStoredProfiles(const QVariantMap &profiles); + void setStoredModuleProviderInfo(const ModuleProviderInfoList &providerInfo); TopLevelProjectPtr loadProject(const SetupProjectParameters ¶meters); static void setupProjectFilePath(SetupProjectParameters ¶meters); @@ -75,6 +77,7 @@ private: QStringList m_searchPaths; std::vector<ProbeConstPtr> m_oldProjectProbes; QHash<QString, std::vector<ProbeConstPtr>> m_oldProductProbes; + ModuleProviderInfoList m_storedModuleProviderInfo; QVariantMap m_storedProfiles; FileTime m_lastResolveTime; }; diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp index b34cfd6a9..c4d77ba40 100644 --- a/src/lib/corelib/language/moduleloader.cpp +++ b/src/lib/corelib/language/moduleloader.cpp @@ -57,6 +57,7 @@ #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> @@ -73,6 +74,8 @@ #include <QtCore/qdiriterator.h> #include <QtCore/qjsondocument.h> #include <QtCore/qjsonobject.h> +#include <QtCore/qtemporaryfile.h> +#include <QtCore/qtextstream.h> #include <QtScript/qscriptvalueiterator.h> #include <algorithm> @@ -267,6 +270,11 @@ void ModuleLoader::setStoredProfiles(const QVariantMap &profiles) m_storedProfiles = profiles; } +void ModuleLoader::setStoredModuleProviderInfo(const ModuleProviderInfoList &moduleProviderInfo) +{ + m_moduleProviderInfo = moduleProviderInfo; +} + ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters ¶meters) { TimedActivityLogger moduleLoaderTimer(m_logger, Tr::tr("ModuleLoader"), @@ -290,6 +298,7 @@ ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters ¶meters) static const QStringList prefixes({ StringConstants::projectPrefix(), QLatin1String("projects"), QLatin1String("products"), QLatin1String("modules"), + StringConstants::moduleProviders(), StringConstants::qbsModule()}); bool ok = false; for (const auto &prefix : prefixes) { @@ -309,6 +318,8 @@ ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters ¶meters) e.append(QLatin1Char('\t') + Tr::tr("modules.<module-name>.<property-name>:value")); e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<module-name>." "<property-name>:value")); + e.append(QLatin1Char('\t') + Tr::tr("moduleProviders.<provider-name>." + "<property-name>:value")); handlePropertyError(e, m_parameters, m_logger); } @@ -456,6 +467,9 @@ private: m_parentItems.push_back(item); for (Item::PropertyMap::const_iterator it = item->properties().constBegin(); it != item->properties().constEnd(); ++it) { + if (item->type() == ItemType::Product && it.key() == StringConstants::moduleProviders() + && it.value()->type() == Value::ItemValueType) + continue; const PropertyDeclaration decl = item->propertyDeclaration(it.key()); if (decl.isValid()) { if (!decl.isDeprecated()) @@ -547,6 +561,9 @@ void ModuleLoader::handleTopLevelProject(ModuleLoaderResult *loadResult, Item *p throw err; handleProductError(err, &productContext); } + for (std::size_t i = 0; i < productContext.newlyAddedModuleProviderSearchPaths.size(); ++i) + m_reader->popExtraSearchPaths(); + productContext.newlyAddedModuleProviderSearchPaths.clear(); } } if (!m_productsWithDeferredDependsItems.empty() || !m_exportsWithDeferredDependsItems.empty()) { @@ -585,6 +602,7 @@ void ModuleLoader::handleTopLevelProject(ModuleLoaderResult *loadResult, Item *p } loadResult->projectProbes = tlp.probes; + loadResult->moduleProviderInfo = m_moduleProviderInfo; m_reader->clearExtraSearchPathsStack(); AccumulatingTimer timer(m_parameters.logElapsedTime() @@ -2111,6 +2129,18 @@ void ModuleLoader::setSearchPathsForProduct(ModuleLoader::ProductContext *produc if (!currentSearchPaths.contains(p) && FileInfo(p).exists()) 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()) == c.config) { + product->knownModuleProviders.insert(c.name); + product->searchPaths << c.searchPaths; + } + } + } } ModuleLoader::ShadowProductInfo ModuleLoader::getShadowProductInfo( @@ -2498,6 +2528,9 @@ void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *pare QString msg = Tr::tr("A Depends item with more than one module cannot have an id."); throw ErrorInfo(msg, dependsItem->location()); } + const FallbackMode fallbackMode = m_parameters.fallbackProviderEnabled() + && m_evaluator->boolValue(dependsItem, StringConstants::enableFallbackProperty()) + ? FallbackMode::Enabled : FallbackMode::Disabled; QList<QualifiedId> moduleNames; const QualifiedId nameParts = QualifiedId::fromString(name); @@ -2554,8 +2587,8 @@ void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *pare QVariantMap defaultParameters; Item *moduleItem = loadModule(dependsContext->product, dependsContext->exportingProductItem, parentItem, dependsItem->location(), dependsItem->id(), - moduleName, multiplexConfigurationIds.first(), isRequired, - &result.isProduct, &defaultParameters); + moduleName, multiplexConfigurationIds.first(), fallbackMode, + isRequired, &result.isProduct, &defaultParameters); if (!moduleItem) { const QString productName = ResolvedProduct::fullDisplayName( dependsContext->product->name, @@ -2746,11 +2779,12 @@ Item *ModuleLoader::moduleInstanceItem(Item *containerItem, const QualifiedId &m return instance; } -ModuleLoader::ProductModuleInfo *ModuleLoader::productModule( - ProductContext *productContext, const QString &name, const QString &multiplexId) +ModuleLoader::ProductModuleInfo *ModuleLoader::productModule(ProductContext *productContext, + const QString &name, const QString &multiplexId, bool &productNameMatch) { auto &exportsData = productContext->project->topLevelProject->productModules; const auto firstIt = exportsData.find(name); + productNameMatch = firstIt != exportsData.end(); for (auto it = firstIt; it != exportsData.end() && it.key() == name; ++it) { if (it.value().multiplexId == multiplexId) return &it.value(); @@ -2861,8 +2895,9 @@ private: Item *ModuleLoader::loadModule(ProductContext *productContext, Item *exportingProductItem, Item *item, const CodeLocation &dependsItemLocation, const QString &moduleId, const QualifiedId &moduleName, - const QString &multiplexId, bool isRequired, - bool *isProductDependency, QVariantMap *defaultParameters) + const QString &multiplexId, FallbackMode fallbackMode, + bool isRequired, bool *isProductDependency, + QVariantMap *defaultParameters) { qCDebug(lcModuleLoader) << "loadModule name:" << moduleName.toString() << "id:" << moduleId; @@ -2900,17 +2935,15 @@ Item *ModuleLoader::loadModule(ProductContext *productContext, Item *exportingPr Item *modulePrototype = nullptr; ProductModuleInfo * const pmi = productModule(productContext, moduleName.toString(), - multiplexId); + multiplexId, *isProductDependency); if (pmi) { - *isProductDependency = true; m_dependsChain.back().isProduct = true; modulePrototype = pmi->exportItem; if (defaultParameters) *defaultParameters = pmi->defaultParameters; - } else { - *isProductDependency = false; + } else if (!*isProductDependency) { modulePrototype = searchAndLoadModuleFile(productContext, dependsItemLocation, - moduleName, isRequired, moduleInstance); + moduleName, fallbackMode, isRequired, moduleInstance); } delayedPropertyChanger.applyNow(); if (!modulePrototype) @@ -2968,17 +3001,19 @@ static Item *chooseModuleCandidate(const std::vector<PrioritizedItem> &candidate Item *ModuleLoader::searchAndLoadModuleFile(ProductContext *productContext, const CodeLocation &dependsItemLocation, const QualifiedId &moduleName, - bool isRequired, Item *moduleInstance) + FallbackMode fallbackMode, bool isRequired, Item *moduleInstance) { bool triedToLoadModule = false; const QString fullName = moduleName.toString(); std::vector<PrioritizedItem> candidates; const QStringList &searchPaths = m_reader->allSearchPaths(); + bool matchingDirectoryFound = false; for (int i = 0; i < searchPaths.size(); ++i) { const QString &path = searchPaths.at(i); const QString dirPath = findExistingModulePath(path, moduleName); if (dirPath.isEmpty()) continue; + matchingDirectoryFound = true; QStringList moduleFileNames = m_moduleDirListCache.value(dirPath); if (moduleFileNames.empty()) { QDirIterator dirIter(dirPath, StringConstants::qbsFileWildcards()); @@ -2999,6 +3034,36 @@ Item *ModuleLoader::searchAndLoadModuleFile(ProductContext *productContext, } if (candidates.empty()) { + if (!matchingDirectoryFound) { + 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); + } + if (result.providerAddedSearchPaths) { + qCDebug(lcModuleLoader) << "Re-checking for module" << moduleName.toString() + << "with newly added search paths from module provider"; + return searchAndLoadModuleFile(productContext, dependsItemLocation, moduleName, + fallbackMode, isRequired, moduleInstance); + } + } if (!isRequired) return createNonPresentModule(fullName, QLatin1String("not found"), nullptr); if (Q_UNLIKELY(triedToLoadModule)) @@ -3177,8 +3242,8 @@ Item::Module ModuleLoader::loadBaseModule(ProductContext *productContext, Item * Item::Module baseModuleDesc; baseModuleDesc.name = baseModuleName; baseModuleDesc.item = loadModule(productContext, nullptr, item, CodeLocation(), QString(), - baseModuleName, QString(), true, &baseModuleDesc.isProduct, - nullptr); + baseModuleName, QString(), FallbackMode::Disabled, true, + &baseModuleDesc.isProduct, nullptr); if (productContext->item) { const Item * const qbsInstanceItem = moduleInstanceItem(productContext->item, baseModuleName); @@ -3599,6 +3664,145 @@ QString ModuleLoader::findExistingModulePath(const QString &searchPath, return dirPath; } +QVariantMap ModuleLoader::moduleProviderConfig(ModuleLoader::ProductContext &product) +{ + if (product.moduleProviderConfigRetrieved) + return product.theModuleProviderConfig; + const ItemValueConstPtr configItemValue + = product.item->itemProperty(StringConstants::moduleProviders()); + if (configItemValue) { + const std::function<void(const Item *, QualifiedId)> collectMap + = [this, &product, &collectMap](const Item *item, 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: + collectMap(static_cast<ItemValue *>(it.value().get())->item(), + QualifiedId(name += it.key())); + return; + 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 = product.theModuleProviderConfig.value(name.toString()).toMap(); + m.insert(it.key(), value); + product.theModuleProviderConfig.insert(name.toString(), m); + } + }; + 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 = product.theModuleProviderConfig.value(provider).toMap(); + for (auto propIt = providerConfigFromBuildConfig.begin(); + propIt != providerConfigFromBuildConfig.end(); ++propIt) { + currentMapForProvider.insert(propIt.key(), propIt.value()); + } + product.theModuleProviderConfig.insert(provider, currentMapForProvider); + } + product.moduleProviderConfigRetrieved = true; + return product.theModuleProviderConfig; +} + +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())); + } + 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); + stream.setCodec("UTF-8"); + 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))); + } + 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::ProbeExecution); + const QStringList searchPaths + = m_evaluator->stringListValue(providerItem, QStringLiteral("searchPaths")); + if (searchPaths.empty()) { + qCDebug(lcModuleLoader) << "Module provider did run, but did not set up " + "any modules."; + return ModuleProviderResult(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 the code that removes the product-specific search paths when + // product handling is done. + // (4) is needed for possible re-use in subsequent products and builds. + m_reader->pushExtraSearchPaths(searchPaths); // (1) + product.searchPaths << searchPaths; // (2) + product.newlyAddedModuleProviderSearchPaths.push_back(searchPaths); // (3) + m_moduleProviderInfo.emplace_back(ModuleProviderInfo(name, moduleConfig.toMap(), // (4) + searchPaths, m_parameters.dryRun())); + return ModuleProviderResult(true, true); + } + return ModuleProviderResult(); +} + void ModuleLoader::setScopeForDescendants(Item *item, Item *scope) { for (Item * const child : item->children()) { @@ -3755,8 +3959,8 @@ void ModuleLoader::addTransitiveDependencies(ProductContext *ctx) } else { Item::Module dep; dep.item = loadModule(ctx, nullptr, ctx->item, ctx->item->location(), QString(), - module.name, QString(), module.required, &dep.isProduct, - &dep.parameters); + module.name, QString(), FallbackMode::Disabled, + module.required, &dep.isProduct, &dep.parameters); if (!dep.item) { throw ErrorInfo(Tr::tr("Module '%1' not found when setting up transitive " "dependencies for product '%2'.").arg(module.name.toString(), diff --git a/src/lib/corelib/language/moduleloader.h b/src/lib/corelib/language/moduleloader.h index 7b9e0bede..9e45908c3 100644 --- a/src/lib/corelib/language/moduleloader.h +++ b/src/lib/corelib/language/moduleloader.h @@ -44,6 +44,7 @@ #include "forward_decls.h" #include "item.h" #include "itempool.h" +#include "moduleproviderinfo.h" #include <logging/logger.h> #include <tools/filetime.h> #include <tools/qttools.h> @@ -106,6 +107,7 @@ struct ModuleLoaderResult Item *root; QHash<Item *, ProductInfo> productInfos; std::vector<ProbeConstPtr> projectProbes; + ModuleProviderInfoList moduleProviderInfo; Set<QString> qbsFiles; QVariantMap profileConfigs; }; @@ -128,6 +130,7 @@ public: void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes); void setLastResolveTime(const FileTime &time) { m_lastResolveTime = time; } void setStoredProfiles(const QVariantMap &profiles); + void setStoredModuleProviderInfo(const ModuleProviderInfoList &moduleProviderInfo); Evaluator *evaluator() const { return m_evaluator; } ModuleLoaderResult load(const SetupProjectParameters ¶meters); @@ -181,6 +184,11 @@ private: std::unordered_map<const Item *, std::vector<ErrorInfo>> unknownProfilePropertyErrors; QStringList searchPaths; + std::vector<QStringList> newlyAddedModuleProviderSearchPaths; + Set<QualifiedId> knownModuleProviders; + QVariantMap theModuleProviderConfig; + bool moduleProviderConfigRetrieved = false; + // The key corresponds to DeferredDependsContext.exportingProductItem, which is the // only value from that data structure that we still need here. std::unordered_map<Item *, std::vector<Item *>> deferredDependsItems; @@ -297,16 +305,18 @@ private: QVariantMap extractParameters(Item *dependsItem) const; Item *moduleInstanceItem(Item *containerItem, const QualifiedId &moduleName); static ProductModuleInfo *productModule(ProductContext *productContext, const QString &name, - const QString &multiplexId); + const QString &multiplexId, bool &productNameMatch); static ProductContext *product(ProjectContext *projectContext, const QString &name); static ProductContext *product(TopLevelProjectContext *tlpContext, const QString &name); + + enum class FallbackMode { Enabled, Disabled }; Item *loadModule(ProductContext *productContext, Item *exportingProductItem, Item *item, const CodeLocation &dependsItemLocation, const QString &moduleId, - const QualifiedId &moduleName, const QString &multiplexId, bool isRequired, - bool *isProductDependency, QVariantMap *defaultParameters); + const QualifiedId &moduleName, const QString &multiplexId, FallbackMode fallbackMode, + bool isRequired, bool *isProductDependency, QVariantMap *defaultParameters); Item *searchAndLoadModuleFile(ProductContext *productContext, const CodeLocation &dependsItemLocation, const QualifiedId &moduleName, - bool isRequired, Item *moduleInstance); + FallbackMode fallbackMode, bool isRequired, Item *moduleInstance); Item *loadModuleFile(ProductContext *productContext, const QString &fullModuleName, bool isBaseModule, const QString &filePath, bool *triedToLoad, Item *moduleInstance); Item *getModulePrototype(ProductContext *productContext, const QString &fullModuleName, @@ -329,6 +339,20 @@ private: Item *wrapInProjectIfNecessary(Item *item); static QString findExistingModulePath(const QString &searchPath, 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); @@ -425,6 +449,8 @@ private: std::unordered_map<ProductContext *, Set<DeferredDependsContext>> m_productsWithDeferredDependsItems; Set<Item *> m_exportsWithDeferredDependsItems; + ModuleProviderInfoList m_moduleProviderInfo; + SetupProjectParameters m_parameters; std::unique_ptr<Settings> m_settings; Version m_qbsVersion; diff --git a/src/lib/corelib/language/moduleproviderinfo.h b/src/lib/corelib/language/moduleproviderinfo.h new file mode 100644 index 000000000..fef9d9765 --- /dev/null +++ b/src/lib/corelib/language/moduleproviderinfo.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** 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 QBS_MODULEPROVIDERINFO_H +#define QBS_MODULEPROVIDERINFO_H + +#include "qualifiedid.h" +#include <tools/persistence.h> + +#include <QtCore/qstringlist.h> +#include <QtCore/qvariant.h> + +#include <vector> + +namespace qbs { +namespace Internal { + +class ModuleProviderInfo +{ +public: + ModuleProviderInfo() = default; + ModuleProviderInfo(const QualifiedId &name, const QVariantMap &config, + const QStringList &searchPaths, bool transientOutput) + : name(name), config(config), searchPaths(searchPaths), transientOutput(transientOutput) + {} + + static QString outputBaseDirName() { return QStringLiteral("genmodules"); } + static QString outputDirPath(const QString &baseDir, const QualifiedId &name) + { + return baseDir + QLatin1Char('/') + outputBaseDirName() + QLatin1Char('/') + + name.toString(); + } + QString outputDirPath(const QString &baseDir) const + { + return outputDirPath(baseDir, name); + } + + template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp<opType>(reinterpret_cast<QStringList &>(name), config, searchPaths); + } + + QualifiedId name; + QVariantMap config; + QStringList searchPaths; + bool transientOutput = false; // Not to be serialized. +}; + +using ModuleProviderInfoList = std::vector<ModuleProviderInfo>; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/language/projectresolver.cpp b/src/lib/corelib/language/projectresolver.cpp index 7deb964b3..d0af0b7ed 100644 --- a/src/lib/corelib/language/projectresolver.cpp +++ b/src/lib/corelib/language/projectresolver.cpp @@ -240,6 +240,7 @@ TopLevelProjectPtr ProjectResolver::resolveTopLevelProject() project->buildSystemFiles = m_loadResult.qbsFiles; project->profileConfigs = m_loadResult.profileConfigs; project->probes = m_loadResult.projectProbes; + project->moduleProviderInfo = m_loadResult.moduleProviderInfo; ProjectContext projectContext; projectContext.project = project; diff --git a/src/lib/corelib/tools/persistence.cpp b/src/lib/corelib/tools/persistence.cpp index ec412cf3b..b50084702 100644 --- a/src/lib/corelib/tools/persistence.cpp +++ b/src/lib/corelib/tools/persistence.cpp @@ -48,7 +48,7 @@ namespace qbs { namespace Internal { -static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-124"; +static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-125"; NoBuildGraphError::NoBuildGraphError(const QString &filePath) : ErrorInfo(Tr::tr("Build graph not found for configuration '%1'. Expected location was '%2'.") diff --git a/src/lib/corelib/tools/setupprojectparameters.cpp b/src/lib/corelib/tools/setupprojectparameters.cpp index 39304d4c9..5600d9b0b 100644 --- a/src/lib/corelib/tools/setupprojectparameters.cpp +++ b/src/lib/corelib/tools/setupprojectparameters.cpp @@ -92,6 +92,7 @@ public: bool logElapsedTime; bool forceProbeExecution; bool waitLockBuildGraph; + bool fallbackProviderEnabled = true; SetupProjectParameters::RestoreBehavior restoreBehavior; ErrorHandlingMode propertyCheckingMode; ErrorHandlingMode productErrorMode; @@ -505,6 +506,22 @@ void SetupProjectParameters::setWaitLockBuildGraph(bool wait) } /*! + * \brief Returns true if qbs should fall back to pkg-config if a dependency is not found. + */ +bool SetupProjectParameters::fallbackProviderEnabled() const +{ + return d->fallbackProviderEnabled; +} + +/*! + * Controls whether to fall back to pkg-config if a dependency is not found. + */ +void SetupProjectParameters::setFallbackProviderEnabled(bool enable) +{ + d->fallbackProviderEnabled = enable; +} + +/*! * \brief Gets the environment used while resolving the project. */ QProcessEnvironment SetupProjectParameters::environment() const diff --git a/src/lib/corelib/tools/setupprojectparameters.h b/src/lib/corelib/tools/setupprojectparameters.h index fe7e3d487..10e4310cd 100644 --- a/src/lib/corelib/tools/setupprojectparameters.h +++ b/src/lib/corelib/tools/setupprojectparameters.h @@ -123,6 +123,9 @@ public: bool waitLockBuildGraph() const; void setWaitLockBuildGraph(bool wait); + bool fallbackProviderEnabled() const; + void setFallbackProviderEnabled(bool enable); + QProcessEnvironment environment() const; void setEnvironment(const QProcessEnvironment &env); QProcessEnvironment adjustedEnvironment() const; diff --git a/src/lib/corelib/tools/stringconstants.h b/src/lib/corelib/tools/stringconstants.h index 1a9356e49..cd41f3768 100644 --- a/src/lib/corelib/tools/stringconstants.h +++ b/src/lib/corelib/tools/stringconstants.h @@ -86,6 +86,7 @@ public: static const QString &explicitlyDependsOnFromDependenciesProperty() { return explicitlyDependsOnFromDependencies(); } + QBS_STRING_CONSTANT(enableFallbackProperty, "enableFallback") static const QString &fileNameProperty() { return fileName(); } static const QString &filePathProperty() { return filePath(); } static const QString &filePathVar() { return filePath(); } @@ -111,6 +112,7 @@ public: QBS_STRING_CONSTANT(limitToSubProjectProperty, "limitToSubProject") QBS_STRING_CONSTANT(minimumQbsVersionProperty, "minimumQbsVersion") QBS_STRING_CONSTANT(moduleNameProperty, "moduleName") + QBS_STRING_CONSTANT(moduleProviders, "moduleProviders") QBS_STRING_CONSTANT(multiplexByQbsPropertiesProperty, "multiplexByQbsProperties") QBS_STRING_CONSTANT(multiplexConfigurationIdProperty, "multiplexConfigurationId") QBS_STRING_CONSTANT(multiplexConfigurationIdsProperty, "multiplexConfigurationIds") |